Skip to content

Commit

Permalink
Add PSR S910 midi transmitter
Browse files Browse the repository at this point in the history
  • Loading branch information
blesswinsamuel committed May 25, 2022
1 parent eece6dc commit 33869ae
Show file tree
Hide file tree
Showing 6 changed files with 801 additions and 123 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
"singleQuote": true,
"printWidth": 160
}
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Layout from './components/Layout'
import { BrowserRouter, Route, Routes, useLocation } from 'react-router-dom'
import Home from './pages/Home/Home'
import MidiMonitor from './pages/MidiMonitor'
import MidiTransmitter from './pages/MidiTransmitter'
import MidiTransmitter from './pages/MidiTransmitter/MidiTransmitter'
import MidiPlayer from './pages/MidiPlayer'
import MidiSynth from './pages/MidiSynth'

Expand Down
10 changes: 6 additions & 4 deletions src/components/WebMidi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const WebMidiContext = createContext<WebMidiContextState>({
export const WebMidiProvider: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => {
const [webMidiEnabled, webMidiError] = useRequestWebMidi()
const [webMidiEnabled, webMidiError] = useRequestWebMidi({ sysex: true })
const { inputs, outputs } = useWebMidiDeviceConnectionListeners()

if (webMidiError !== null) {
Expand All @@ -39,7 +39,9 @@ export const WebMidiProvider: React.FC<{ children?: React.ReactNode }> = ({
)
}

function useRequestWebMidi(): [boolean, Error | null] {
export function useRequestWebMidi(options?: {
sysex?: boolean
}): [boolean, Error | null] {
const [webMidiEnabled, setWebMidiEnabled] = useState(false)
const [webMidiError, setWebMidiError] = useState<Error | null>(null)

Expand All @@ -50,15 +52,15 @@ function useRequestWebMidi(): [boolean, Error | null] {
}
;(async () => {
try {
await WebMidi.enable()
await WebMidi.enable(options)
console.log('WebMidi enabled')
AppToaster.show({ message: `WebMidi enabled` })
setWebMidiEnabled(true)
} catch (err) {
setWebMidiError(err as Error)
}
})()
}, [setWebMidiEnabled, setWebMidiError])
}, [setWebMidiEnabled, setWebMidiError, options])

return [webMidiEnabled, webMidiError]
}
Expand Down
109 changes: 38 additions & 71 deletions src/pages/MidiMonitor.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,27 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import useLocalStorageState from '../components/hooks/useLocalStorageState'
import { WebMidi, Event } from 'webmidi'
import { WebMidi, Event, ControlChangeMessageEvent, NoteMessageEvent, MessageEvent } from 'webmidi'
import MidiDeviceSelector from '../components/MidiDeviceSelector'
import {
Button,
Checkbox,
ControlGroup,
FormGroup,
NumericInput,
} from '@blueprintjs/core'
import { Button, Checkbox, ControlGroup, FormGroup, NumericInput } from '@blueprintjs/core'

const eventTypes: { [key: string]: (e: any) => string } = {
activesensing: (e: any) => '',
clock: (e: any) => '',
controlchange: (e: { controller: { number: any; name: any }; value: any }) =>
[e.controller.number, e.controller.name, e.value].join(' '),
noteon: (e: { note: { name: any; octave: any }; rawVelocity: any }) =>
[e.note.name + e.note.octave, e.rawVelocity].join(' '),
noteoff: (e: { note: { name: any; octave: any }; rawVelocity: any }) =>
[e.note.name + e.note.octave, e.rawVelocity].join(' '),
controlchange: (e: ControlChangeMessageEvent) => [e.controller.number, e.controller.name, e.value].join(' '),
noteon: (e: NoteMessageEvent) => [e.note.name + e.note.octave, e.rawValue].join(' '),
noteoff: (e: NoteMessageEvent) => [e.note.name + e.note.octave, e.rawValue].join(' '),
sysex: (e: MessageEvent) =>
Array.from(e.message.rawData.values())
.map((x) => x.toString(16).padStart(2, '0').toUpperCase())
.join(' '),
}

export default function MidiMonitor() {
const [logs, setLogs] = useState('')
const [tempo, setTempo] = useLocalStorageState('midi:monitor:tempo', 100)
const [deviceId, setDeviceId] = useLocalStorageState(
'midi:monitor:device',
''
)
const [selectedEventTypes, setEventTypes] = useLocalStorageState(
'midi:monitor:eventTypes',
['noteon']
)
const [selectedChannels, setChannels] = useLocalStorageState(
'midi:monitor:channels',
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
)
const [deviceId, setDeviceId] = useLocalStorageState('midi:monitor:device', '')
const [selectedEventTypes, setEventTypes] = useLocalStorageState('midi:monitor:eventTypes', ['noteon'])
const [selectedChannels, setChannels] = useLocalStorageState('midi:monitor:channels', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])

const device = WebMidi.getInputById(deviceId)

Expand All @@ -46,11 +32,7 @@ export default function MidiMonitor() {
}

useEffect(() => {
const listener = (e: {
type: keyof Event
timestamp: number
channel: { toString: () => string }
}) => {
const listener = (e: { type: keyof Event; timestamp: number; channel: { toString: () => string } }) => {
const getMessage = eventTypes[e.type as any] as any
if (!getMessage) {
console.warn(`No getMessage function for ${e.type}`)
Expand All @@ -73,15 +55,16 @@ export default function MidiMonitor() {
.join('\n')
)
}
const options = {
channels: selectedChannels,
}
if (device) {
selectedEventTypes.forEach((eventType: any) => {
device.addListener(eventType, listener, { channels: selectedChannels })
device.addListener(eventType, listener, options)
})
return () => {
selectedEventTypes.forEach((eventType: any) => {
device.removeListener(eventType, listener, {
channels: selectedChannels,
})
device.removeListener(eventType, listener, options)
})
}
}
Expand All @@ -97,22 +80,15 @@ export default function MidiMonitor() {
<div>
<FormGroup>
<ControlGroup>
<MidiDeviceSelector
mode="input"
label="Input"
value={deviceId}
onChange={(v: any) => setDeviceId(v)}
/>
<MidiDeviceSelector mode="input" label="Input" value={deviceId} onChange={(v: any) => setDeviceId(v)} />
<NumericInput
leftIcon="time"
// rightElement={<Tag minimal>bpm</Tag>}
placeholder="Tempo"
value={tempo}
min={32}
max={240}
onChange={(e: { target: { value: any } }) =>
setTempo(e.target.value)
}
onChange={(e: { target: { value: any } }) => setTempo(e.target.value)}
/>
<Button onClick={clear}>Clear</Button>
</ControlGroup>
Expand All @@ -127,9 +103,7 @@ export default function MidiMonitor() {
if ((e.target as HTMLInputElement).checked) {
setEventTypes((t: any) => [...t, eventType])
} else {
setEventTypes((t: any[]) =>
t.filter((e: string) => e !== eventType)
)
setEventTypes((t: any[]) => t.filter((e: string) => e !== eventType))
}
}}
>
Expand All @@ -138,31 +112,24 @@ export default function MidiMonitor() {
))}
</FormGroup>
<FormGroup label="Channels">
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map(
(channel) => (
<Checkbox
key={channel}
inline={true}
checked={selectedChannels.includes(channel)}
onChange={(e) => {
if ((e.target as HTMLInputElement).checked) {
setChannels((ch: any) => [...ch, channel])
} else {
setChannels((ch: any[]) =>
ch.filter((e: number) => e !== channel)
)
}
}}
>
{channel}
</Checkbox>
)
)}
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map((channel) => (
<Checkbox
key={channel}
inline={true}
checked={selectedChannels.includes(channel)}
onChange={(e) => {
if ((e.target as HTMLInputElement).checked) {
setChannels((ch: any) => [...ch, channel])
} else {
setChannels((ch: any[]) => ch.filter((e: number) => e !== channel))
}
}}
>
{channel}
</Checkbox>
))}
</FormGroup>
<pre
ref={logRef}
className="bg-black bg-opacity-20 overflow-y-scroll h-[500px]"
>
<pre ref={logRef} className="bg-black bg-opacity-20 overflow-y-scroll h-[500px]">
<code className="py-4 block">{logs}</code>
</pre>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React, { useState } from 'react'
import useLocalStorageState from '../components/hooks/useLocalStorageState'
import useLocalStorageState from '../../components/hooks/useLocalStorageState'
import { WebMidi, Output } from 'webmidi'
import MidiDeviceSelector from '../components/MidiDeviceSelector'
import Select from '../components/Select'
import MidiDeviceSelector from '../../components/MidiDeviceSelector'
import Select from '../../components/Select'
import {
Button,
FormGroup,
HTMLTable,
InputGroup,
NumericInput,
} from '@blueprintjs/core'
import PsrS910 from './PsrS910'

const options = [
'psr-s910',
'playNote',
'send',
'sendActiveSensing',
Expand Down Expand Up @@ -41,48 +43,6 @@ const options = [
'stopNote',
]

export const handleFormSubmit = (func: any) => (event: any) => {
event.preventDefault()
return func()
}

export default function MidiTransmitter() {
const [deviceId, setDeviceId] = useLocalStorageState(
'midi:transmitter:device',
''
)
const [method, setMethod] = useLocalStorageState(
'midi:transmitter:method',
''
)
const [state, setState] = useState({})

const device = WebMidi.getOutputById(deviceId)

console.log(device)

return (
<div>
<FormGroup label="Device">
<MidiDeviceSelector
mode="output"
label="Output"
value={deviceId}
onChange={(v) => setDeviceId(v)}
/>
</FormGroup>
<FormGroup label="Event">
<Select
options={['', ...options]}
value={method}
onChange={(event) => setMethod(event.currentTarget.value)}
/>
</FormGroup>
{renderMethod(method, device, state, setState)}
</div>
)
}

const methods: {
[key: string]: {
fields: (keyof typeof fieldTypes)[]
Expand Down Expand Up @@ -179,12 +139,57 @@ const fieldTypes = {
program: 'string', // check if this is right
}

export const handleFormSubmit = (func: any) => (event: any) => {
event.preventDefault()
return func()
}

export default function MidiTransmitter() {
const [deviceId, setDeviceId] = useLocalStorageState(
'midi:transmitter:device',
''
)
const [method, setMethod] = useLocalStorageState(
'midi:transmitter:method',
''
)
const [state, setState] = useState({})

const device = WebMidi.getOutputById(deviceId)

console.log(device)

return (
<div>
<FormGroup label="Device">
<MidiDeviceSelector
mode="output"
label="Output"
value={deviceId}
onChange={(v) => setDeviceId(v)}
/>
</FormGroup>
<FormGroup label="Event">
<Select
options={['', ...options]}
value={method}
onChange={(event) => setMethod(event.currentTarget.value)}
/>
</FormGroup>
{renderMethod(method, device, state, setState)}
</div>
)
}

const renderMethod = (
method: string,
device: Output | false,
device: Output,
state: any,
setState: any
) => {
if (method === 'psr-s910') {
return <PsrS910 device={device} />
}
const m = methods[method]
if (!m) {
return <div>Not implemented</div>
Expand Down
Loading

0 comments on commit 33869ae

Please sign in to comment.