diff options
| author | s-ol <s+removethis@s-ol.nu> | 2021-01-20 23:15:14 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2021-01-22 13:45:11 +0000 |
| commit | 61a9215b410686ed587c6681fe86d181d895beac (patch) | |
| tree | 5118631f517c909480b7bd4217c77fd877c7f8ff | |
| parent | add shell.nix (diff) | |
| download | isomorphic-kb-explorer-61a9215b410686ed587c6681fe86d181d895beac.tar.gz isomorphic-kb-explorer-61a9215b410686ed587c6681fe86d181d895beac.zip | |
add different layouts
| -rw-r--r-- | app/app.js | 135 | ||||
| -rw-r--r-- | app/config.js | 13 | ||||
| -rw-r--r-- | app/css.js | 12 | ||||
| -rw-r--r-- | app/notes.js | 14 | ||||
| -rw-r--r-- | app/style.js | 86 |
5 files changed, 181 insertions, 79 deletions
@@ -1,5 +1,6 @@ import React from 'react'; -import css from './css'; +import { pos, height } from './style'; +import notes from './notes'; const statbyte = (stat, chan) => (stat << 4) | chan; const NOTE_ON = 0b1001; @@ -23,59 +24,8 @@ const parse = message => ({ val: message.data[2] / 127, }); -const longSize = 3; -const rowStaggered = true; -const w = longSize * (rowStaggered ? Math.sqrt(3) : 2 ); -const h = longSize * (rowStaggered ? 2 : Math.sqrt(3)); -const sx = longSize * (rowStaggered ? Math.sqrt(3) : 1.5 ); -const sy = longSize * (rowStaggered ? 1.5 : Math.sqrt(3)); -const rem = (n) => `${n}rem`; - -css(` -body { - background: #212121; - margin: 2rem; - - font-family: sans-serif; -} - -.app, .keyboard { - position: relative; -} - -.hexagon { - position: absolute; - width: ${rem(w)}; - height: ${rem(h)}; -} - -.hexagon > .inner { - margin: auto; - width: ${rem(Math.min(w, h) * 0.8)}; - height: ${rem(Math.min(w, h) * 0.8)}; - padding: ${rem(longSize * 0.4)}; - border-radius: ${rem(longSize)}; - box-sizing: border-box; - font-size: ${rem(longSize * 0.5)}; - text-align: center; - background: #696969; - color: #eeeeee; - cursor: pointer; - - transition: background 300ms; -} -.hexagon > .inner:hover { - background: #848484; -} -.hexagon > .inner:active, -.hexagon.active > .inner { - background: #eeeeee; - color: #696969; -} -`); - -const Hexagon = ({ x, y, children, state, note, send }) => { - if (rowStaggered) { +const Hexagon = ({ x, y, children, state, note, send, major }) => { + if (major == 'row') { if (y % 2 == 0) x += 0.5; } else { @@ -86,10 +36,7 @@ const Hexagon = ({ x, y, children, state, note, send }) => { return ( <div className={'hexagon' + (state ? ' active' : '')} - style={{ - top: rem(y * sy), - left: rem(x * sx), - }} + style={pos([x, y], major)} > <div className="inner" @@ -102,6 +49,33 @@ const Hexagon = ({ x, y, children, state, note, send }) => { ); }; +const layouts = { + wicki_hayden: { + major: 'row', + map: ([x, y], [w, h]) => { + let note = 48 + 2 * x + 6 * y; + if (y % 2 == 0) note += 1; + return note; + }, + }, + harmonic: { + major: 'col', + map: ([x, y], [w, h]) => { + let note = 48 + x * 0.5 + y * 7; + if (x % 2 === 1) note += 3.5; + return note; + }, + }, + gerhard: { + major: 'col', + map: ([x, y], [w, h]) => { + let note = 48 + x * 3.5 + y; + if (x % 2 === 1) note -= 0.5; + return note; + }, + }, +}; + const range = i => new Array(i).fill(true); class Keyboard extends React.Component { @@ -120,14 +94,19 @@ class Keyboard extends React.Component { } render() { - const { w, h, send } = this.props; + const { w, h, send, layout, showMidi } = this.props; + const { major, map } = layouts[layout]; return ( - <div className="keyboard"> + <div + className={`keyboard ${major}-major`} + style={{ + height: height(h, major), + }} + > {range(w).map((_, x) => ( range(h).map((_, y) => { - let note = 48 + 2 * x + 6 * (h - y); - if (y % 2 == 0) note += 1; + const note = map([x, y], [w, h]); return ( <Hexagon key={x + ',' + y} @@ -135,8 +114,9 @@ class Keyboard extends React.Component { note={note} state={this.state[note]} send={send} + major={major} > - {note} + {showMidi ? notes[note % 12] : note} </Hexagon> ); }) @@ -149,6 +129,10 @@ class Keyboard extends React.Component { export default class App extends React.Component { keyboardRef = React.createRef(); + state = { + layout: 'wicki_hayden', + }; + componentDidUpdate(prevProps) { if (prevProps.midiin) prevProps.midiin.onmidimessage = null; @@ -178,10 +162,33 @@ export default class App extends React.Component { this.props.midiout.send(msg); } + onlayout = (e) => { + this.setState({ layout: e.target.value }); + } + render() { + const { configure, showMidi } = this.props; + const { layout } = this.state; + return ( <div className="app"> - <Keyboard ref={this.keyboardRef} w={10} h={4} send={this.send} /> + <button onClick={configure}>settings</button> + <label>layout: + <select name="layout" onChange={this.onlayout} value={layout}> + <option value="wicki_hayden">Wicki-Hayden</option> + <option value="harmonic">Harmonic Table</option> + <option value="gerhard">Gerhard</option> + </select> + </label> + + <Keyboard + ref={this.keyboardRef} + w={10} + h={4} + send={this.send} + layout={layout} + showMidi={showMidi} + /> </div> ); } diff --git a/app/config.js b/app/config.js index e5f2b0c..a59d989 100644 --- a/app/config.js +++ b/app/config.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import css from './css'; +import { css } from './style'; const setState = (key, state) => { try { @@ -83,7 +83,7 @@ export default WrappedComponent => constructor(props) { super(props); - this.state = loadState('settings', { configured: false, midiSupport: false }); + this.state = loadState('settings', { configured: false, midiSupport: false, showMidi: false }); this.configure = () => this.setState({ configured: false }); this.reload = () => { @@ -119,12 +119,13 @@ export default WrappedComponent => } render() { - const { configured, midiin, midiout } = this.state; + 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} @@ -134,6 +135,12 @@ export default WrappedComponent => 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" @@ -1,13 +1 @@ -export default (code) => { - const style = document.createElement('style'); - document.head.appendChild(style); - style.appendChild(document.createTextNode('')); - let i = 0; - for (const v of code.split('}')) { - if (v.indexOf('{') < 0) continue; - style.sheet.insertRule(v + '}', i++); - } - - return style; -}; diff --git a/app/notes.js b/app/notes.js new file mode 100644 index 0000000..3d0e659 --- /dev/null +++ b/app/notes.js @@ -0,0 +1,14 @@ +export default { + 11: 'B', + 10: 'A#', + 9: 'A', + 8: 'G#', + 7: 'G', + 6: 'F#', + 5: 'F', + 4: 'E', + 3: 'D#', + 2: 'D', + 1: 'C#', + 0: 'C', +}; diff --git a/app/style.js b/app/style.js new file mode 100644 index 0000000..f2e1b7c --- /dev/null +++ b/app/style.js @@ -0,0 +1,86 @@ +export const css = (code) => { + const style = document.createElement('style'); + document.head.appendChild(style); + style.appendChild(document.createTextNode('')); + + let i = 0; + for (const v of code.split('}')) { + if (v.indexOf('{') < 0) continue; + style.sheet.insertRule(v + '}', i++); + } + + return style; +}; + +const longSize = 3; +const sizeA = longSize * Math.sqrt(3); +const sizeB = longSize * 2; +const strideA = longSize * Math.sqrt(3); +const strideB = longSize * 1.5; +const rem = (n) => `${n}rem`; + +export const pos = ([x, y], major) => { + let [sx, sy] = major === 'row' + ? [strideA, strideB] + : [strideB, strideA]; + return { + bottom: rem(y * sy), + left: rem(x * sx), + }; +}; + +export const height = (h, major) => { + if (major === 'row') + return rem(h * strideA); + + return rem((h + 1.5) * strideB); +}; + +css(` +body { + color: #eeeeee; + background: #212121; + margin: 2rem; + + font-family: sans-serif; +} + +.app, .keyboard { + position: relative; +} + +.hexagon { + position: absolute; + width: ${rem(sizeA)}; + height: ${rem(sizeB)}; +} + +.col-major .hexagon { + width: ${rem(sizeB)}; + height: ${rem(sizeA)}; +} + +.hexagon > .inner { + margin: auto; + width: ${rem(longSize * 1.4)}; + height: ${rem(longSize * 1.4)}; + padding: ${rem(longSize * 0.4)}; + border-radius: ${rem(longSize)}; + box-sizing: border-box; + font-size: ${rem(longSize * 0.5)}; + text-align: center; + background: #696969; + color: #eeeeee; + cursor: pointer; + + transition: background 300ms; +} +.hexagon > .inner:hover { + background: #848484; +} +.hexagon > .inner:active, +.hexagon.active > .inner { + background: #eeeeee; + color: #696969; +} +`); |
