How to add a custom DirtEventType for MIDI over OSC
jarmitage opened this issue · 10 comments
I have an OSC protocol here for an instrument that sends MIDI NoteOn and NoteOff messages over OSC, plus some custom sound parameters also being sent over OSC:
- SuperCollider client: https://github.com/Intelligent-Instruments-Lab/iil-python-tools/blob/master/examples/mrp/sc/MRP.sc
- Python client for same instrument: https://github.com/Intelligent-Instruments-Lab/iil-python-tools/blob/master/mrp/mrp/mrp.py#L57-L78
I typical event might look like:
/mrp/midi Channel16NoteOn NoteNumber NoteVelocity
/mrp/quality/intensity Channel16 NoteNumber IntensityValue
/mrp/quality/brightness Channel16 NoteNumber BrightnessValue
/mrp/midi Channel16NoteOff NoteNumber
I assume to get this working with SuperDirt, I would need to add a custom DirtEventType similar to midiEvent
? I tried to read the code in DirtEvent for how SuperDirt MIDI works, but I found the code really hard to read.
Any pointers would be appreciated.
This seems like a midiesque OSC protocol rather than MIDI over OSC. Why not just send the OSC from Tidal?
Yeah that's right, but what I am struggling with is how would I get the NoteOff messages scheduled automatically like SuperDirt MIDI does?
https://github.com/musikinformatik/SuperDirt/blob/develop/classes/DirtEventTypes.sc#L126-L128
How about something like this:
import Data.Maybe (fromJust)
eventHasOffset :: Event a -> Bool
eventHasOffset e | isAnalog e = False
| otherwise = stop (fromJust $ whole e) == stop (part e)
offsets :: Pattern a -> Pattern a
offsets pat = withEvent f $ filterEvents eventHasOffset pat
where f (e@(Event _ Nothing _ _)) = e -- ignore analog events
f (Event c (Just (Arc b e)) _ v) = Event c (Just a) a v
where a = Arc e (e + (e - b))
cmd = pS "cmd"
noteonoff pat = stack [pat # cmd "Channel16NoteOn", offsets pat # cmd "Channel16NoteOff"]
noteonoff (note "a b" # velocity 50)
giving:
(0>½)|cmd: "Channel16NoteOn", note: 9.0n (a5), velocity: 50.0f
(½>1)|cmd: "Channel16NoteOn", note: 11.0n (b5), velocity: 50.0f
(½>1)|cmd: "Channel16NoteOff", note: 9.0n (a5), velocity: 50.0f
(1>1½)|cmd: "Channel16NoteOff", note: 11.0n (b5), velocity: 50.0f
Getting the OSC shape right from this would hopefully be straightforward, as long as the MRP accepts (and ignores) the velocity parameter on a note off message.
Btw. there is already a midi event type in SuperDirt's DirtEventTypes
. For tests, you could even tweak it while running.
(
DirtEventTypes.midiEvent[\play] = {
<.... the long function ....>
}
)
The tidal solution for sending noteoffs does have some drawbacks, e.g. if you silence the pattern then the noteoff won't get sent. I think it's better to modify the receiving end to accept a duration parameter to the noteon. Tidal will fill this in to a parameter called 'delta'.
Thank you for the suggestions.
A Haskell-only OSCTarget version sounds great...
@yaxu: if you silence the pattern then the noteoff won't get sent
There is an /mrp/allnotesoff
command in the spec, so I could get around this with e.g. hushmrp = once $ s "mrp" $ mrp_msg "allnotesoff
and equivalents for silence
. I'm going to try this out with your example code...
@yaxu: I think it's better to modify the receiving end to accept a duration parameter to the noteon.
Unfortunately the receiving end is a 10+ year old C++ codebase (albeit a marvel in its own right!) that I can't build myself in XCode as of yet. That's why I was wondering about using our SuperCollider MRP client class and creating a custom DirtEventType
to have more control over the messaging/scheduling.
@telephon: there is already a midi event type in SuperDirt's DirtEventTypes
Yes this is what I was originally referring to. As I mentioned, I found this quite difficult to comprehend, as I'm a novice SC user, but also there's few code comments and no .schelp
for this class. I assume I would need the overall structure/logic of the midiEvent
type to be able to schedule the NoteOff messages (ref https://github.com/musikinformatik/SuperDirt/blob/develop/classes/DirtEventTypes.sc#L107-L131), but to send out OSC messages instead of MIDI messages?
A thought: since our MRP class has its own MIDIdef
s as inputs, could this somehow be added as a "MIDI device" in the regular SuperDirt MIDI fashion?
I had a quick go at the Haskell-only version. There's two head-scratchers:
The first is that the keyword-value OSC message style of Tidal means I can't send a message that complies with the keyword-less spec of the MRP (i.e. /mrp/midi Channel16NoteOn NoteNumber NoteVelocity
is sent as /mrp/midi 145 48 127
). If there's no simple way around this in Haskell, then I would need to parse out the keywords, probably in SC which wouldn't necessarily be a bad thing... however...
The second is that the MRP app cannot currently schedule incoming messages such as...
(0>½)|cmd: "Channel16NoteOn", note: 9.0n (a5), velocity: 50.0f
(½>1)|cmd: "Channel16NoteOn", note: 11.0n (b5), velocity: 50.0f
(½>1)|cmd: "Channel16NoteOff", note: 9.0n (a5), velocity: 50.0f
(1>1½)|cmd: "Channel16NoteOff", note: 11.0n (b5), velocity: 50.0f
Hopefully I will be able to build the MRP app someday, however in this case I would be less sure about designing a C++ Tidal event scheduler for it, over using what SuperDirt already has implemented.
This maybe leads back to the idea of a SuperDirt DirtEventType. Unless ~dirt.addMidi
can somehow send MIDI out to the MRP SC class directly using its MIDIdef
s? I guess it could simply use a virtual MIDI device as the go-between.
Use 'arglist' to send a list of values rather than name-value pairs http://tidalcycles.org/docs/configuration/MIDIOSC/osc/#defining-osc-message-structure
However it seems the only difference between a note on and note off is the lack of a velocity parameter in the latter. So you'll need to do some hackery to get around that.
I'd forget about MIDI and superdirt. It'd be easier to make an OSC server for converting between tidal deltas and the MRP protocol.
I'll close this for now as I don't think it makes sense to add OSC functionality to the MIDI code.
Yes that seems right.
I'm finding a way through with the Haskell OSC side now, thanks.
For posterity, here's the thing working: https://github.com/Intelligent-Instruments-Lab/iil-python-tools/tree/ja-dev/examples/mrp/tidalcycles