0 | 0 |
import React from 'react';
|
1 | |
import { pos, height } from './style';
|
2 | |
import notes from './notes';
|
|
1 |
import { pos, width, height } from './style';
|
|
2 |
import * as notes from './notes';
|
3 | 3 |
|
4 | 4 |
const statbyte = (stat, chan) => (stat << 4) | chan;
|
5 | 5 |
const NOTE_ON = 0b1001;
|
|
77 | 77 |
|
78 | 78 |
|
79 | 79 |
const range = i => new Array(i).fill(true);
|
80 | |
class Keyboard extends React.Component {
|
81 | |
state = {};
|
82 | |
|
83 | |
onmessage = (message) => {
|
84 | |
switch (message.cmd) {
|
85 | |
case NOTE_ON:
|
86 | |
this.setState(old => ({ ...old, [message.note]: true }));
|
87 | |
break;
|
88 | |
|
89 | |
case NOTE_OFF:
|
90 | |
this.setState(old => ({ ...old, [message.note]: false }));
|
91 | |
break;
|
92 | |
}
|
93 | |
}
|
94 | |
|
95 | |
render() {
|
96 | |
const { w, h, send, layout, showMidi } = this.props;
|
97 | |
const { major, map } = layouts[layout];
|
98 | |
|
99 | |
return (
|
100 | |
<div
|
101 | |
className={`keyboard ${major}-major`}
|
102 | |
style={{
|
103 | |
height: height(h, major),
|
104 | |
}}
|
105 | |
>
|
106 | |
{range(w).map((_, x) => (
|
107 | |
range(h).map((_, y) => {
|
108 | |
const note = map([x, y], [w, h]);
|
109 | |
return (
|
110 | |
<Hexagon
|
111 | |
key={x + ',' + y}
|
112 | |
x={x} y={y}
|
113 | |
note={note}
|
114 | |
state={this.state[note]}
|
115 | |
send={send}
|
116 | |
major={major}
|
117 | |
>
|
118 | |
{showMidi ? notes[note % 12] : note}
|
119 | |
</Hexagon>
|
120 | |
);
|
121 | |
})
|
122 | |
))}
|
123 | |
</div>
|
124 | |
);
|
125 | |
}
|
126 | |
}
|
|
80 |
const Keyboard = ({ w, h, send, layout: { major, map }, showMidi, state }) => (
|
|
81 |
<div
|
|
82 |
className={`keyboard ${major}-major`}
|
|
83 |
style={{
|
|
84 |
height: height(h, major),
|
|
85 |
width: width(w, major),
|
|
86 |
}}
|
|
87 |
>
|
|
88 |
{range(w).map((_, x) => (
|
|
89 |
range(h).map((_, y) => {
|
|
90 |
const note = map([x, y], [w, h]);
|
|
91 |
return (
|
|
92 |
<Hexagon
|
|
93 |
key={x + ',' + y}
|
|
94 |
x={x} y={y}
|
|
95 |
note={note}
|
|
96 |
state={state[note]}
|
|
97 |
send={send}
|
|
98 |
major={major}
|
|
99 |
>
|
|
100 |
{showMidi ? note : notes.music[note % 12]}
|
|
101 |
</Hexagon>
|
|
102 |
);
|
|
103 |
})
|
|
104 |
))}
|
|
105 |
</div>
|
|
106 |
);
|
|
107 |
|
|
108 |
const ChordView = ({ chord }) => {
|
|
109 |
const min = chord[0];
|
|
110 |
|
|
111 |
return (
|
|
112 |
<div className="chord">
|
|
113 |
{range(13).map((_,i) => (
|
|
114 |
<div
|
|
115 |
key={i}
|
|
116 |
className="blip"
|
|
117 |
/>
|
|
118 |
))}
|
|
119 |
{chord.map((n) => (
|
|
120 |
<div
|
|
121 |
key={n}
|
|
122 |
className="note"
|
|
123 |
style={{ bottom: `${((n - min) % 12) / 12 * 100 - 3}%` }}
|
|
124 |
>
|
|
125 |
{(n - min) && (<span>+{n - min}</span>)}
|
|
126 |
</div>
|
|
127 |
))}
|
|
128 |
</div>
|
|
129 |
);
|
|
130 |
};
|
127 | 131 |
|
128 | 132 |
export default class App extends React.Component {
|
129 | |
keyboardRef = React.createRef();
|
130 | |
|
131 | 133 |
state = {
|
132 | 134 |
layout: 'wicki_hayden',
|
|
135 |
notes: {},
|
133 | 136 |
};
|
|
137 |
|
|
138 |
componentDidMount() {
|
|
139 |
document.addEventListener("keydown", this.onkeydown);
|
|
140 |
document.addEventListener("keyup", this.onkeyup);
|
|
141 |
}
|
|
142 |
|
|
143 |
componentWillUnmount() {
|
|
144 |
document.removeEventListener("keydown", this.onkeydown);
|
|
145 |
document.removeEventListener("keyup", this.onkeyup);
|
|
146 |
}
|
134 | 147 |
|
135 | 148 |
componentDidUpdate(prevProps) {
|
136 | 149 |
if (prevProps.midiin)
|
|
141 | 154 |
}
|
142 | 155 |
|
143 | 156 |
blink = (note) => {
|
144 | |
if (this.keyboardRef.current) {
|
145 | |
const { current } = this.keyboardRef;
|
146 | |
current.onmessage({ cmd: NOTE_ON, note });
|
147 | |
setTimeout(() => current.onmessage({ cmd: NOTE_OFF, note }), 250);
|
148 | |
}
|
|
157 |
this.onmessage({ cmd: NOTE_ON, note });
|
|
158 |
setTimeout(() => this.onmessage({ cmd: NOTE_OFF, note }), 250);
|
|
159 |
}
|
|
160 |
|
|
161 |
send = (command, note) => {
|
|
162 |
if (!this.props.midiout)
|
|
163 |
return;
|
|
164 |
|
|
165 |
const msg = [statbyte(command, 0), note, 127];
|
|
166 |
this.props.midiout.send(msg);
|
|
167 |
}
|
|
168 |
|
|
169 |
onlayout = (e) => {
|
|
170 |
this.setState({ layout: e.target.value });
|
149 | 171 |
}
|
150 | 172 |
|
151 | 173 |
onmessage = (e) => {
|
152 | 174 |
const message = parse(e);
|
153 | |
|
154 | |
if (this.keyboardRef.current)
|
155 | |
this.keyboardRef.current.onmessage(message);
|
156 | 175 |
|
157 | 176 |
switch (message.cmd) {
|
158 | 177 |
case NOTE_ON:
|
159 | 178 |
console.info(message.note);
|
160 | 179 |
break;
|
161 | 180 |
}
|
162 | |
}
|
163 | |
|
164 | |
send = (command, note) => {
|
165 | |
if (!this.props.midiout)
|
166 | |
return;
|
167 | |
|
168 | |
const msg = [statbyte(command, 0), note, 127];
|
169 | |
this.props.midiout.send(msg);
|
170 | |
}
|
171 | |
|
172 | |
onlayout = (e) => {
|
173 | |
this.setState({ layout: e.target.value });
|
|
181 |
|
|
182 |
switch (message.cmd) {
|
|
183 |
case NOTE_ON:
|
|
184 |
this.setState(({ notes }) => ({ notes: { ...notes, [message.note]: true } }));
|
|
185 |
break;
|
|
186 |
|
|
187 |
case NOTE_OFF:
|
|
188 |
this.setState(({ notes }) => ({ notes: { ...notes, [message.note]: false } }));
|
|
189 |
break;
|
|
190 |
}
|
|
191 |
}
|
|
192 |
|
|
193 |
onkeydown = (e) => {
|
|
194 |
const note = notes.key2midi[e.code];
|
|
195 |
if (!note) return;
|
|
196 |
e.preventDefault();
|
|
197 |
this.setState(({ notes }) => ({ notes: { ...notes, [note]: true } }));
|
|
198 |
}
|
|
199 |
|
|
200 |
onkeyup = (e) => {
|
|
201 |
const note = notes.key2midi[e.code];
|
|
202 |
if (!note) return;
|
|
203 |
e.preventDefault();
|
|
204 |
this.setState(({ notes }) => ({ notes: { ...notes, [note]: false } }));
|
174 | 205 |
}
|
175 | 206 |
|
176 | 207 |
render() {
|
177 | 208 |
const { configure, showMidi } = this.props;
|
178 | |
const { layout } = this.state;
|
|
209 |
const { layout, notes } = this.state;
|
|
210 |
|
|
211 |
const chord = Object.entries(notes).filter(([note, on]) => on).map(([k, _]) => k);
|
|
212 |
chord.sort();
|
179 | 213 |
|
180 | 214 |
return (
|
181 | 215 |
<div className="app">
|
|
188 | 222 |
</select>
|
189 | 223 |
</label>
|
190 | 224 |
|
191 | |
<Keyboard
|
192 | |
ref={this.keyboardRef}
|
193 | |
w={12}
|
194 | |
h={4}
|
195 | |
send={this.send}
|
196 | |
layout={layout}
|
197 | |
showMidi={showMidi}
|
198 | |
/>
|
|
225 |
<div className="main">
|
|
226 |
<Keyboard
|
|
227 |
w={12}
|
|
228 |
h={4}
|
|
229 |
send={this.send}
|
|
230 |
layout={layouts[layout]}
|
|
231 |
showMidi={showMidi}
|
|
232 |
state={notes}
|
|
233 |
/>
|
|
234 |
<ChordView
|
|
235 |
chord={chord}
|
|
236 |
/>
|
|
237 |
</div>
|
199 | 238 |
</div>
|
200 | 239 |
);
|
201 | 240 |
}
|