git.s-ol.nu isomorphic-kb-explorer / 61a9215
add different layouts s-ol 1 year, 3 months ago
5 changed file(s) with 181 addition(s) and 79 deletion(s). Raw diff Collapse all Expand all
00 import React from 'react';
1 import css from './css';
1 import { pos, height } from './style';
2 import notes from './notes';
23
34 const statbyte = (stat, chan) => (stat << 4) | chan;
45 const NOTE_ON = 0b1001;
2223 val: message.data[2] / 127,
2324 });
2425
25 const longSize = 3;
26 const rowStaggered = true;
27 const w = longSize * (rowStaggered ? Math.sqrt(3) : 2 );
28 const h = longSize * (rowStaggered ? 2 : Math.sqrt(3));
29 const sx = longSize * (rowStaggered ? Math.sqrt(3) : 1.5 );
30 const sy = longSize * (rowStaggered ? 1.5 : Math.sqrt(3));
31 const rem = (n) => `${n}rem`;
32
33 css(`
34 body {
35 background: #212121;
36 margin: 2rem;
37
38 font-family: sans-serif;
39 }
40
41 .app, .keyboard {
42 position: relative;
43 }
44
45 .hexagon {
46 position: absolute;
47 width: ${rem(w)};
48 height: ${rem(h)};
49 }
50
51 .hexagon > .inner {
52 margin: auto;
53 width: ${rem(Math.min(w, h) * 0.8)};
54 height: ${rem(Math.min(w, h) * 0.8)};
55 padding: ${rem(longSize * 0.4)};
56 border-radius: ${rem(longSize)};
57 box-sizing: border-box;
58 font-size: ${rem(longSize * 0.5)};
59 text-align: center;
60 background: #696969;
61 color: #eeeeee;
62 cursor: pointer;
63
64 transition: background 300ms;
65 }
66 .hexagon > .inner:hover {
67 background: #848484;
68 }
69 .hexagon > .inner:active,
70 .hexagon.active > .inner {
71 background: #eeeeee;
72 color: #696969;
73 }
74 `);
75
76 const Hexagon = ({ x, y, children, state, note, send }) => {
77 if (rowStaggered) {
26 const Hexagon = ({ x, y, children, state, note, send, major }) => {
27 if (major == 'row') {
7828 if (y % 2 == 0)
7929 x += 0.5;
8030 } else {
8535 return (
8636 <div
8737 className={'hexagon' + (state ? ' active' : '')}
88 style={{
89 top: rem(y * sy),
90 left: rem(x * sx),
91 }}
38 style={pos([x, y], major)}
9239 >
9340 <div
9441 className="inner"
9946 </div>
10047 </div>
10148 );
49 };
50
51 const layouts = {
52 wicki_hayden: {
53 major: 'row',
54 map: ([x, y], [w, h]) => {
55 let note = 48 + 2 * x + 6 * y;
56 if (y % 2 == 0) note += 1;
57 return note;
58 },
59 },
60 harmonic: {
61 major: 'col',
62 map: ([x, y], [w, h]) => {
63 let note = 48 + x * 0.5 + y * 7;
64 if (x % 2 === 1) note += 3.5;
65 return note;
66 },
67 },
68 gerhard: {
69 major: 'col',
70 map: ([x, y], [w, h]) => {
71 let note = 48 + x * 3.5 + y;
72 if (x % 2 === 1) note -= 0.5;
73 return note;
74 },
75 },
10276 };
10377
10478
11993 }
12094
12195 render() {
122 const { w, h, send } = this.props;
96 const { w, h, send, layout, showMidi } = this.props;
97 const { major, map } = layouts[layout];
12398
12499 return (
125 <div className="keyboard">
100 <div
101 className={`keyboard ${major}-major`}
102 style={{
103 height: height(h, major),
104 }}
105 >
126106 {range(w).map((_, x) => (
127107 range(h).map((_, y) => {
128 let note = 48 + 2 * x + 6 * (h - y);
129 if (y % 2 == 0) note += 1;
108 const note = map([x, y], [w, h]);
130109 return (
131110 <Hexagon
132111 key={x + ',' + y}
134113 note={note}
135114 state={this.state[note]}
136115 send={send}
116 major={major}
137117 >
138 {note}
118 {showMidi ? notes[note % 12] : note}
139119 </Hexagon>
140120 );
141121 })
147127
148128 export default class App extends React.Component {
149129 keyboardRef = React.createRef();
130
131 state = {
132 layout: 'wicki_hayden',
133 };
150134
151135 componentDidUpdate(prevProps) {
152136 if (prevProps.midiin)
177161 this.props.midiout.send(msg);
178162 }
179163
164 onlayout = (e) => {
165 this.setState({ layout: e.target.value });
166 }
167
180168 render() {
169 const { configure, showMidi } = this.props;
170 const { layout } = this.state;
171
181172 return (
182173 <div className="app">
183 <Keyboard ref={this.keyboardRef} w={10} h={4} send={this.send} />
174 <button onClick={configure}>settings</button>
175 <label>layout:
176 <select name="layout" onChange={this.onlayout} value={layout}>
177 <option value="wicki_hayden">Wicki-Hayden</option>
178 <option value="harmonic">Harmonic Table</option>
179 <option value="gerhard">Gerhard</option>
180 </select>
181 </label>
182
183 <Keyboard
184 ref={this.keyboardRef}
185 w={10}
186 h={4}
187 send={this.send}
188 layout={layout}
189 showMidi={showMidi}
190 />
184191 </div>
185192 );
186193 }
00 import React from 'react';
11 import ReactDOM from 'react-dom';
2 import css from './css';
2 import { css } from './style';
33
44 const setState = (key, state) => {
55 try {
8282 constructor(props) {
8383 super(props);
8484
85 this.state = loadState('settings', { configured: false, midiSupport: false });
85 this.state = loadState('settings', { configured: false, midiSupport: false, showMidi: false });
8686 this.configure = () => this.setState({ configured: false });
8787
8888 this.reload = () => {
118118 }
119119
120120 render() {
121 const { configured, midiin, midiout } = this.state;
121 const { configured, midiin, midiout, showMidi } = this.state;
122122
123123 if (configured) {
124124 return (
125125 <WrappedComponent
126126 {...this.props}
127 {...this.state}
127128 midiin={this.inputs && this.inputs.get(midiin)}
128129 midiout={this.outputs && this.outputs.get(midiout)}
129130 configure={this.configure}
133134
134135 return (
135136 <div className="panel settings">
137 <h3>Display</h3>
138 <Checkbox
139 name="Show notes as MIDI number"
140 set={this.set('showMidi')}
141 value={showMidi}
142 />
136143 <h3>MIDI I/O</h3>
137144 <Dropdown
138145 name="MIDI Keyboard Input"
0 export default (code) => {
1 const style = document.createElement('style');
2 document.head.appendChild(style);
3 style.appendChild(document.createTextNode(''));
40
5 let i = 0;
6 for (const v of code.split('}')) {
7 if (v.indexOf('{') < 0) continue;
8 style.sheet.insertRule(v + '}', i++);
9 }
10
11 return style;
12 };
0 export default {
1 11: 'B',
2 10: 'A#',
3 9: 'A',
4 8: 'G#',
5 7: 'G',
6 6: 'F#',
7 5: 'F',
8 4: 'E',
9 3: 'D#',
10 2: 'D',
11 1: 'C#',
12 0: 'C',
13 };
0 export const css = (code) => {
1 const style = document.createElement('style');
2 document.head.appendChild(style);
3 style.appendChild(document.createTextNode(''));
4
5 let i = 0;
6 for (const v of code.split('}')) {
7 if (v.indexOf('{') < 0) continue;
8 style.sheet.insertRule(v + '}', i++);
9 }
10
11 return style;
12 };
13
14 const longSize = 3;
15 const sizeA = longSize * Math.sqrt(3);
16 const sizeB = longSize * 2;
17 const strideA = longSize * Math.sqrt(3);
18 const strideB = longSize * 1.5;
19 const rem = (n) => `${n}rem`;
20
21 export const pos = ([x, y], major) => {
22 let [sx, sy] = major === 'row'
23 ? [strideA, strideB]
24 : [strideB, strideA];
25 return {
26 bottom: rem(y * sy),
27 left: rem(x * sx),
28 };
29 };
30
31 export const height = (h, major) => {
32 if (major === 'row')
33 return rem(h * strideA);
34
35 return rem((h + 1.5) * strideB);
36 };
37
38 css(`
39 body {
40 color: #eeeeee;
41 background: #212121;
42 margin: 2rem;
43
44 font-family: sans-serif;
45 }
46
47 .app, .keyboard {
48 position: relative;
49 }
50
51 .hexagon {
52 position: absolute;
53 width: ${rem(sizeA)};
54 height: ${rem(sizeB)};
55 }
56
57 .col-major .hexagon {
58 width: ${rem(sizeB)};
59 height: ${rem(sizeA)};
60 }
61
62 .hexagon > .inner {
63 margin: auto;
64 width: ${rem(longSize * 1.4)};
65 height: ${rem(longSize * 1.4)};
66 padding: ${rem(longSize * 0.4)};
67 border-radius: ${rem(longSize)};
68 box-sizing: border-box;
69 font-size: ${rem(longSize * 0.5)};
70 text-align: center;
71 background: #696969;
72 color: #eeeeee;
73 cursor: pointer;
74
75 transition: background 300ms;
76 }
77 .hexagon > .inner:hover {
78 background: #848484;
79 }
80 .hexagon > .inner:active,
81 .hexagon.active > .inner {
82 background: #eeeeee;
83 color: #696969;
84 }
85 `);