aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2021-01-20 23:15:14 +0000
committers-ol <s+removethis@s-ol.nu>2021-01-22 13:45:11 +0000
commit61a9215b410686ed587c6681fe86d181d895beac (patch)
tree5118631f517c909480b7bd4217c77fd877c7f8ff
parentadd shell.nix (diff)
downloadisomorphic-kb-explorer-61a9215b410686ed587c6681fe86d181d895beac.tar.gz
isomorphic-kb-explorer-61a9215b410686ed587c6681fe86d181d895beac.zip
add different layouts
-rw-r--r--app/app.js135
-rw-r--r--app/config.js13
-rw-r--r--app/css.js12
-rw-r--r--app/notes.js14
-rw-r--r--app/style.js86
5 files changed, 181 insertions, 79 deletions
diff --git a/app/app.js b/app/app.js
index 8679784..5f78b4a 100644
--- a/app/app.js
+++ b/app/app.js
@@ -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"
diff --git a/app/css.js b/app/css.js
index 842214a..8b13789 100644
--- a/app/css.js
+++ b/app/css.js
@@ -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;
+}
+`);