diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-10-04 12:19:25 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-10-04 12:19:25 +0000 |
| commit | 1ab9ed5b297048c5d3bc0278eddeb5a1d1c80367 (patch) | |
| tree | 929cd8dd9eaa99894e4f7da9b2ad0f6e5be1abd6 /app/app.js | |
| download | isomorphic-kb-explorer-1ab9ed5b297048c5d3bc0278eddeb5a1d1c80367.tar.gz isomorphic-kb-explorer-1ab9ed5b297048c5d3bc0278eddeb5a1d1c80367.zip | |
initial commit
Diffstat (limited to 'app/app.js')
| -rw-r--r-- | app/app.js | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/app/app.js b/app/app.js new file mode 100644 index 0000000..27c0e38 --- /dev/null +++ b/app/app.js @@ -0,0 +1,155 @@ +import React from 'react'; +import css from './css'; + +const statbyte = (stat, chan) => (stat << 4) | chan; +const noteon = (key, vel=127, chan=0) => [statbyte(0b1001, chan), key, vel]; +const noteoff = (key, vel=127, chan=0) => [statbyte(0b1000, chan), key, vel]; +const ccchange = (ctl, val, chan=0) => [statbyte(0b1011, chan), ctl, val]; + +const clamp = (val, min=0, max=1) => Math.max(min, Math.min(val, max)); +const ramp = i => new Array(i).fill(true).map((_, i) => i); +const mix = (i, a, b) => i * a + (1-i) * b; + +window.ccchange = ccchange; +window.noteon = noteon; + +const parse = message => ({ + cmd: message.data[0] >> 4, + chan: message.data[0] & 0xf, + note: message.data[1], + 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 { + 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 }) => { + if (rowStaggered) { + if (y % 2 == 0) + x += 0.5; + } else { + if (x % 2 == 0) + y += 0.5; + } + + return ( + <div + className={'hexagon' + (state ? ' active' : '')} + style={{ + top: rem(y * sy), + left: rem(x * sx), + }} + > + <div + className="inner" + onClick={() => console.info(x,y)} + > + {children} + </div> + </div> + ); +}; + + +const range = i => new Array(i).fill(true); + +export default class App extends React.Component { + state = {}; + + componentDidUpdate(prevProps) { + if (prevProps.midiin) + prevProps.midiin.onmidimessage = null; + + if (this.props.midiin) + this.props.midiin.onmidimessage = this.onmessage; + } + + onmessage = (e) => { + const message = parse(e); + + switch (message.cmd) { + case 9: + // NOTE_ON + this.setState(old => ({ ...old, [message.note]: true })); + break; + + case 8: + // NOTE_OFF + this.setState(old => ({ ...old, [message.note]: false })); + break; + + case 11: + // CC + break; + } + } + + render() { + const xs = 10; + const ys = 4; + + return ( + <div className="app"> + {range(xs).map((_, x) => ( + range(ys).map((_, y) => { + let note = 36 + 2 * x + 6 * (ys - y); + if (y % 2 == 0) note += 1; + return ( + <Hexagon key={x + ',' + y} x={x} y={y} note={note} state={this.state[note]}> + {note} + </Hexagon> + ); + }) + ))} + </div> + ); + } +}; |
