aboutsummaryrefslogtreecommitdiffstats
path: root/app/config.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/config.js')
-rw-r--r--app/config.js278
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>
+ );
+};