jack2
jack2 copied to clipboard
Osc support (2nd try)
As of #513, I hacked together a prototype for OSC support. It compiles on my system, and it won't compile any of the modules I don't currently use (fixing them is not that much work, just simply change the header). It hasn't been tested yet.
- Everything that is related to MIDI, but not unique to MIDI has been renamed to form a new "Event" API.
- The old API is still available using header files (
typedef
for the win).- Internal code must use a new
JackMidi.h
that does the type renaming. I could have kept all the old header files so that all the remaining code still works, but I didn't like the idea of having a dozen of almost empty files in the code base.
- Internal code must use a new
- The MIDI channel type has been complemented with an OSC one
- I'm not sure about the MIDI-specific things. Especially the MIDI drivers, since I don't see any equivalent for not-hardware-driven protocols like OSC
- Net support is not clear at all. I'd like to pass all event ports using the existing MIDI code since changing the headers would break things between JACK versions I can't simply make backwards-compatible.
Feel free to object any of the design decisions.
This is quite nice! Thanks for all the work so far.
Curious, how are bundles going to be handled here?
Can you add an example tool that uses this renamed API for OSC?
Curious, how are bundles going to be handled here?
I thought I'd keep it simple: JACK is only the transport server and does not parse the sent data at all. This is the current behavior with MIDI (granted a few exceptions) and I'm not sure if I should change this. Clients may or may not send message bundles. They will be treated as event messages as any other message by JACK. Clients should make sure their messages are not too large (maybe we should increase the default buffer size?). Since each OSC event has its own time stamp system, event order for bundles is not that important.
Can you add an example tool that uses this renamed API for OSC?
Should I put it into the example_clients
folder or should I rather create a new repository for it?
I thought I'd keep it simple: JACK is only the transport server and does not parse the sent data at all.
That makes a lot of sense actually, I am quite okay with this. It is different from what the previous jack_osc project does, but that is quite fair.
Should I put it into the
example_clients
folder or should I rather create a new repository for it?
In the examples folder, I think such a tool could be the go-to example way of doing things.
I imagine something simple that puts jack osc messages into a ring buffer, and then passes them off to the network via liblo. CLI arguments define the protocol to use (UDP vs TCP) and the port.
jack_midi_dump
can serve as an example as it uses a ring buffer to print messages out of the RT process function.
what do you think?
@falkTX I got a few ideas for useful OSC tools. I think I'm going to start with as simple MIDI->OSC->MIDI bridge and escalate as needed. Would you mind the example being in Rust?
Actually yes. rust does not build for all the targets that jack does, so using rust would be quite limiting. Please stick to C or worst case C++.
I'm sold. \m/ nice going, this kinda rules!
Hi
There currently are at least 2 projects already doing OSC via JACK, afaik.
- zynaddsubfx (stable)
- synthpod (unstable)
They just reuse the JACK MIDI ports and API unchanged. To discriminate OSC ports from JACK ports, they are tagged with metadata. The port metadata has been defined by @drobilla at a LAC some years back [1]:
"http://jackaudio.org/metadata/event-types" "OSC"
Some of those metadata keys seems to have been imported into JACK2 source, but not all, it seems @falkTX ?
This does not break anything and is backwards compatible to the beginning of JACK, because OSC and MIDI can coexist on the same event port:
First byte of OSC is either '/' or '#', both are no valid first MIDI byte. So a application handling only MIDI messages, will just ignore OSC and vice-versa.
So, a new event port is really not needed, imho and tagging ports with the correct metadata is super simple.
It also has the advantage that it is easier to extend. e.g. say I want to route LV2 atoms via JACK ports (I've actually done that), I only need to define a new metadata key and make sure that the first byte does not clash with either MIDI or OSC. Going the dedicated-event-port-per- event-type-route, this will always need a new JACK release and be backwards incompatible.
If there really should ever be the need for a new event port type, then please take inspiration from LV2 and create an event port type for all possible future event types and extend the event structure with a proper type, will make any further request for new port types superfluous:
typedef struct _jack_event_event
{
jack_nframes_t time; /**< Sample index at which event is valid */
size_t size; /**< Number of bytes of data in \a buffer */
uint32_t type; 🌹*<Type of this event */
jack_event_data_t *buffer; /**< Raw MIDI data */
} jack_event_event_t;
[1] https://github.com/drobilla/jackey/blob/master/jackey.h
zynaddsubfx has a simple header with some macro definitions[1] and then just uses that [2] + tagging the port with the proper metadata [3].
[1] https://github.com/zynaddsubfx/zynaddsubfx/blob/master/src/Nio/jack_osc.h [2] https://github.com/zynaddsubfx/zynaddsubfx/blob/master/src/Nio/JackEngine.cpp#L320 [3] https://github.com/zynaddsubfx/zynaddsubfx/blob/master/src/Nio/JackEngine.cpp#L249
@ventosus This is cool. My goal is to be fully backwards compatible to existing solutions like these. I am probably going to remove the different port types soon. The only thing I don't like is having to be "MIDI-compatible" regarding the start byte. If the metadata things works properly, we can rely on our patchbays to warn from accidental port content mismatch.
@ventosus If the metadata things works properly, we can rely on our patchbays to warn from accidental port content mismatch.
Now that JACK2 finally has metadata support, too, more projects could actually use this, yes.
Instead of warning, patchbays should just refuse to connect differing event ports (jack_connect could be adapted as an example). patchage [1] does already do that since years, iirc (when run on JACK1, because JACK2 is a late adopter of metadata :-). patchmatrix [2], too.
[1] http://drobilla.net/software/patchage [2] https://open-music-kontrollers.ch/lad/patchmatrix/
@piagemesde
If there's no new event structure, no a new port type, an additional header (e.g. jack/eventport.h) with some defines and typedefs like [1] would be enough, imho (e.g. just replace osc with event).
[1] https://github.com/zynaddsubfx/zynaddsubfx/blob/master/src/Nio/jack_osc.h
last time I checked, JACK OSC messages were not routed via either netjack1, netjack2 or both ?
Needs some investigation, I guess...
@ventosus If I just add a new header, this means that we will stick internally with processing "MIDI" even if in reality the buffers may contain all sorts of data. I fear that this will lead to confusion or even bugs.
Honestly, I have no clue how to update netjack while keeping backwards compatibility. We can rename the MIDI ports to message ports with ease, but a) I don't know how MIDI-specific the data handling is here and b) I don't know if netjack handles metadata yet.
@x42 Please have a look at https://github.com/piegamesde/jack2/commit/8614e5da97a6ead73b19ea7e305aa87677a916c2. I redid that commit (with slightly different naming) from scratch to get a proper diff.
@ventosus Do we really need JACK_METADATA_EVENT_TYPES
to be able to contain multiple values? This is quite a pain to process, honestly, and I don't really see the use (you could still split up the port to one per protocol, or only tag the most prominent one).
@ventosus Do we really need
JACK_METADATA_EVENT_TYPES
to be able to contain multiple values? This is quite a pain to process, honestly, and I don't really see the use (you could still split up the port to one per protocol, or only tag the most prominent one).
@piegamesde probably not (and the linked projects don't do that, either). @drobilla as the author of that key may remember the rationale for it, I don't remember, sorry.
It's for multiplexing. Though it's slightly more annoying to deal with the metadata, this is nothing compared to having to deal with a bunch of separate event ports of different types, compared to a single one with a well-ordered sequence of events. Aside from being a massive hassle to deal with in a sample accurate context, many ports is also quite a bit slower.
This way you can also extend an app to support OSC (or whatever) without completely breaking its interface or making routing weird, because you can naively route around the "control events" without really having to care what they are.
Generally speaking, this stuff is trying to pretend that event ports are the way they should have been: a sequence of time stamped whatevers. I agree it's not pleasant, but c'est la vie.
@drobilla So I'm going to write something to check port compatibility, hide the ugliness in helper methods. What are the semantics of an output port having multiple protocols? As I understand it, that port may emit events in any of those.
Say I have a port that may send OSC and MIDI data. Can I connect this to OSC ports or must these ports implement both OSC and MIDI as well? I'd think the latter one, because that's how you do type safety in programming languages. (Does this matter anyway? Are there even ports that can send both MIDI and OSC?)
@drobilla Instead of allowing multiple values in this field, what about defining one value that has the meaning of "any protocol"? You'd still be able to do multiplex routing MIDI and OSC into one port, but without the pain of having to handle a list of elements.
But then you can't tell what it supports (this is also roughly how it works in LV2). I get that it's mildly more annoying to use than simple one value properties, but.... oh well? I don't think the few lines of C that it takes to look for something in a list outweighs the actual usefulness.
It's a compromise because metadata keys can only have one value. Since this is the general way you deal with that, some utilities for it will probably come in handy anyway.
An output port that has both may send both.
I'll try to actually read this branch in detail tomorrow and hopefully get a better idea of how this ties in with the metadata stuff.