import Keyboard, { StyledNaturalKey, StyledAccidentalKey } from '@theoryofnekomata/react-musical-keyboard' import * as React from 'react' interface ChannelData { channel: number key: number velocity: number } const RANGES = { 'C3-C5': '48|72', 'C3-C6': '48|84', 'C2-C6': '36|84', 'C2-C7': '36|96', 'E1-E7': '28|100', 'E1-G7': '28|103', 'A0-C8': '21|108', 'Full MIDI': '0|127' } const App: React.FC = () => { const [queryDeviceKey, setQueryDeviceKey] = React.useState( () => new URLSearchParams(window.location.search).get('queryDeviceKey') || '' ) const [range, setRange] = React.useState( () => new URLSearchParams(window.location.search).get('range') || RANGES['A0-C8'] ) const [scaleFactor, setScaleFactor] = React.useState(() => Number(new URLSearchParams(window.location.search).get('scaleFactor') || 1) ) const [devicesLoadedTimestamp, setDevicesLoadedTimestamp] = React.useState() const [midiAccess, setMidiAccess] = React.useState() const [unaCorda, setUnaCorda] = React.useState(0) const [sostenuto, setSostenuto] = React.useState(0) const [sustain, setSustain] = React.useState(0) const [keyChannels, setKeyChannels] = React.useState([]) interface MidiMessageEvent extends Event { data: [number, number, number] } const handleChangeRange: React.ChangeEventHandler = (e) => { const { value } = e.currentTarget setRange(value) window.electron.ipcRenderer.send('rangechange', value) } const handleChangeDevice: React.ChangeEventHandler = (e) => { const { value } = e.currentTarget setQueryDeviceKey((oldDeviceKey) => { midiAccess?.inputs.get(oldDeviceKey)?.close() return value }) if (queryDeviceKey) { window.electron.ipcRenderer.send('querydevicekeychange', value) } } const handleChangeScaleFactor: React.ChangeEventHandler = (e) => { const { value: valueRaw } = e.currentTarget const value = Number(valueRaw) setScaleFactor(value) if (typeof value !== 'undefined') { window.electron.ipcRenderer.send('scalefactorchange', value) window.document.documentElement.style.setProperty('--size-scale-factor', value.toString()) } } React.useEffect(() => { window.navigator.requestMIDIAccess().then((midiAccess) => { setMidiAccess(midiAccess) const inputDevices = Array.from(midiAccess.inputs.entries()) const search = new URLSearchParams(window.location.search) const loadedQueryDeviceKey = search.get('queryDeviceKey') || inputDevices[0][0] setQueryDeviceKey(loadedQueryDeviceKey) if (loadedQueryDeviceKey) { window.electron.ipcRenderer.send('querydevicekeychange', loadedQueryDeviceKey) } setDevicesLoadedTimestamp(Date.now()) }) }, []) React.useEffect(() => { const search = new URLSearchParams(window.location.search) const scaleFactorRaw = search.get('scaleFactor') || '1' const scaleFactorTryNumber = Number(scaleFactorRaw) const scaleFactor = Number.isFinite(scaleFactorTryNumber) ? scaleFactorTryNumber : 1 setScaleFactor(scaleFactor) }, []) React.useEffect(() => { const devices = typeof midiAccess?.inputs !== 'undefined' ? Array.from(midiAccess.inputs) : undefined if (typeof devices === 'undefined') { return } if (typeof queryDeviceKey === 'undefined') { return } const currentDeviceEntry = devices.find(([key]) => key === queryDeviceKey) if (typeof currentDeviceEntry === 'undefined') { return } const [, currentDevice] = currentDeviceEntry const listener = (e: Event): void => { if (e.type !== 'midimessage') { return } const midiEvent = e as MidiMessageEvent const [messageType, param1, param2] = midiEvent.data switch (messageType & 0b11110000) { case 0b10110000: { const controlNumber = param1 const value = param2 switch (controlNumber) { case 64: setSustain(value) return case 66: setSostenuto(value) return case 67: setUnaCorda(value) return default: break } return } case 0b10010000: { const channel = messageType & 0b00001111 const keyNumber = param1 const velocity = param2 setKeyChannels((oldKeyChannels) => { if (velocity > 0) { return [ ...oldKeyChannels, { channel, key: keyNumber, velocity } ] } return oldKeyChannels.filter((c) => !(c.channel === channel && c.key === keyNumber)) }) return } case 0b10000000: { const channel = messageType & 0b00001111 const keyNumber = param1 setKeyChannels((oldKeyChannels) => { return oldKeyChannels.filter((c) => !(c.channel === channel && c.key === keyNumber)) }) return } default: break } } currentDevice.addEventListener('midimessage', listener) return () => { currentDevice.removeEventListener('midimessage', listener) } }, [queryDeviceKey, midiAccess]) const [startKeyRaw, endKeyRaw] = range.split('|') const startKey = Number(startKeyRaw) const endKey = Number(endKeyRaw) const devices = typeof midiAccess?.inputs !== 'undefined' ? Array.from(midiAccess.inputs) : undefined return (
) } export default App