0 | 0 |
import React from 'react';
|
1 | |
import css from './css';
|
|
1 |
import { pos, height } from './style';
|
|
2 |
import notes from './notes';
|
2 | 3 |
|
3 | 4 |
const statbyte = (stat, chan) => (stat << 4) | chan;
|
4 | 5 |
const NOTE_ON = 0b1001;
|
|
22 | 23 |
val: message.data[2] / 127,
|
23 | 24 |
});
|
24 | 25 |
|
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') {
|
78 | 28 |
if (y % 2 == 0)
|
79 | 29 |
x += 0.5;
|
80 | 30 |
} else {
|
|
85 | 35 |
return (
|
86 | 36 |
<div
|
87 | 37 |
className={'hexagon' + (state ? ' active' : '')}
|
88 | |
style={{
|
89 | |
top: rem(y * sy),
|
90 | |
left: rem(x * sx),
|
91 | |
}}
|
|
38 |
style={pos([x, y], major)}
|
92 | 39 |
>
|
93 | 40 |
<div
|
94 | 41 |
className="inner"
|
|
99 | 46 |
</div>
|
100 | 47 |
</div>
|
101 | 48 |
);
|
|
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 |
},
|
102 | 76 |
};
|
103 | 77 |
|
104 | 78 |
|
|
119 | 93 |
}
|
120 | 94 |
|
121 | 95 |
render() {
|
122 | |
const { w, h, send } = this.props;
|
|
96 |
const { w, h, send, layout, showMidi } = this.props;
|
|
97 |
const { major, map } = layouts[layout];
|
123 | 98 |
|
124 | 99 |
return (
|
125 | |
<div className="keyboard">
|
|
100 |
<div
|
|
101 |
className={`keyboard ${major}-major`}
|
|
102 |
style={{
|
|
103 |
height: height(h, major),
|
|
104 |
}}
|
|
105 |
>
|
126 | 106 |
{range(w).map((_, x) => (
|
127 | 107 |
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]);
|
130 | 109 |
return (
|
131 | 110 |
<Hexagon
|
132 | 111 |
key={x + ',' + y}
|
|
134 | 113 |
note={note}
|
135 | 114 |
state={this.state[note]}
|
136 | 115 |
send={send}
|
|
116 |
major={major}
|
137 | 117 |
>
|
138 | |
{note}
|
|
118 |
{showMidi ? notes[note % 12] : note}
|
139 | 119 |
</Hexagon>
|
140 | 120 |
);
|
141 | 121 |
})
|
|
147 | 127 |
|
148 | 128 |
export default class App extends React.Component {
|
149 | 129 |
keyboardRef = React.createRef();
|
|
130 |
|
|
131 |
state = {
|
|
132 |
layout: 'wicki_hayden',
|
|
133 |
};
|
150 | 134 |
|
151 | 135 |
componentDidUpdate(prevProps) {
|
152 | 136 |
if (prevProps.midiin)
|
|
177 | 161 |
this.props.midiout.send(msg);
|
178 | 162 |
}
|
179 | 163 |
|
|
164 |
onlayout = (e) => {
|
|
165 |
this.setState({ layout: e.target.value });
|
|
166 |
}
|
|
167 |
|
180 | 168 |
render() {
|
|
169 |
const { configure, showMidi } = this.props;
|
|
170 |
const { layout } = this.state;
|
|
171 |
|
181 | 172 |
return (
|
182 | 173 |
<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 |
/>
|
184 | 191 |
</div>
|
185 | 192 |
);
|
186 | 193 |
}
|