diff options
Diffstat (limited to 'app/config.js')
| -rw-r--r-- | app/config.js | 278 |
1 files changed, 141 insertions, 137 deletions
diff --git a/app/config.js b/app/config.js index a59d989..eac34f8 100644 --- a/app/config.js +++ b/app/config.js @@ -1,14 +1,13 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; +import React, { useState, useCallback, useEffect } from 'react'; +import { labels as noteLabels } from './notes'; import { css } from './style'; -const setState = (key, state) => { +const saveState = (key, state) => { try { localStorage.setItem(key, JSON.stringify(state)); - return true; } catch (e) { - return false; + console.error(e); } }; @@ -22,141 +21,146 @@ const loadState = (key, defaultState=null) => { } }; -css(` - .settings { - width: 500px; - margin: auto; - padding: 1em; - text-align: left; - } - - .setting { - margin: 0.4em 0; - } - - .setting > label { - display: inline-block; - width: 200px; - } - - .setting > select, .setting > input, .settings button { - width: 200px; - margin-left: 20px; - } +export const useAppState = (defaultState) => { + const [state, setState] = useState(defaultState); - .setting > select:disabled { - color: #aaa; - background: #444; - } + useEffect(() => setState(loadState('settings', defaultState)), []); + useEffect(() => saveState('settings', state), [state]); - .settings > button { - text-align: center; - margin-right: 20px; - } -`); + return [state, setState]; +}; -const Dropdown = ({ name, list, set, disabled, value }) => ( - <div className="setting"> - <label>{name}</label> - <select value={value} onChange={e => set(e, e.target.value)} disabled={!list || disabled}> - <option>(none)</option> - {list && ([...list.values()]).map((port) => ( - <option value={port.id} key={port.id}>{port.name}</option> - ))} - </select> - </div> +export const useMIDI = () => { + const [inputs, setInputs] = useState(new Map()); + const [outputs, setOutputs] = useState(new Map()); + + const reload = useCallback(() => { + navigator.requestMIDIAccess && + navigator.requestMIDIAccess({ sysex: true }) + .then((access) => { + setInputs(access.inputs); + setOutputs(access.outputs); + }); + }); + + useEffect(reload, []); + + return { inputs, outputs, reload }; +} + +const Dropdown = ({ list, name, onChange, value }) => ( + <select value={value} onChange={onChange} disabled={!list || !list.size}> + <option>(none)</option> + {list && ([...list.values()]).map((port) => ( + <option value={port.id} key={port.id}>{port.name}</option> + ))} + </select> ); -const Checkbox = ({ name, set, value }) => ( - <div className="setting"> - <label>{name}</label> - <input - type="checkbox" - onChange={e => set(e, !value)} - checked={value} - /> - </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; } +`); -export default WrappedComponent => - class SettingsStore extends React.Component { - constructor(props) { - super(props); - - this.state = loadState('settings', { configured: false, midiSupport: false, showMidi: false }); - this.configure = () => this.setState({ configured: false }); - - this.reload = () => { - navigator.requestMIDIAccess({ sysex: true }) - .then(access => { - this.inputs = access.inputs; - this.outputs = access.outputs; - this.setState({ - midiSupport: true, - }); - this.forceUpdate(); - }); - } - } - - componentWillUpdate(props, state) { - setState('settings', state); - } - - componentDidMount() { - if (navigator.requestMIDIAccess) - this.reload(); - } - - set(k, v) { - return (e, vv) => { - e.stopPropagation(); - - this.setState({ [k]: v === undefined - ? vv - : v }); - } - } - - render() { - const { configured, midiin, midiout, showMidi } = this.state; - - if (configured) { - return ( - <WrappedComponent - {...this.props} - {...this.state} - midiin={this.inputs && this.inputs.get(midiin)} - midiout={this.outputs && this.outputs.get(midiout)} - configure={this.configure} - /> - ); - } - - return ( - <div className="panel settings"> - <h3>Display</h3> - <Checkbox - name="Show notes as MIDI number" - set={this.set('showMidi')} - value={showMidi} - /> - <h3>MIDI I/O</h3> - <Dropdown - name="MIDI Keyboard Input" - list={this.inputs} - set={this.set('midiin')} - value={midiin} - /> - <Dropdown - name="MIDI Output" - list={this.outputs} - set={this.set('midiout')} - value={midiout} - /> - <button onClick={this.reload}>reload MIDI device list</button> - <button onClick={this.set('configured', true)}>start</button> - </div> - ); - }; - }; +export const Toolbar = ({ state, setState, midi }) => { + const { labels, offset, transpose } = state; + + const track = (key) => ({ + name: key, + value: state[key], + onChange: (e) => { + e.stopPropagation(); + let value = e.target.value; + if (key === 'w' || key === 'h') value = +value; + setState({ ...state, [key]: value }); + }, + }); + + return ( + <nav> + <div className="group"> + <label>layout:</label> + <select {...track('layout')}> + <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 {...track('labels')}> + <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={() => setState({ ...state, offset: null })}> + {offset === null + ? "listening..." + : (noteLabels[labels] ? noteLabels[labels][offset % 12] : offset)} + </button> + <select {...track('scale')}> + <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={() => setState({ ...state, transpose: transpose - 12 })}>-</button> + {Math.floor(transpose / 12)} + <button onClick={() => setState({ ...state, transpose: transpose + 12 })}>+</button> + </div> + <div className="group"> + <label>size:</label> + <input className="small" type="number" min="1" {...track('w')} /> + {'x'} + <input className="small" type="number" min="1" {...track('h')} /> + </div> + <div className="group"> + <label>midi</label> + in: + <Dropdown + name="midiin" + list={midi.inputs} + {...track('midiin')} + /> + out: + <Dropdown + name="midiout" + list={midi.outputs} + {...track('midiout')} + /> + <button onClick={midi.reload}>↻</button> + </div> + <div className="spacer" /> + </nav> + ); +}; |
