I’m working on SDK v2.1 which has some features I saw requested on this forum, plus it has some helpers for supporting streaming wav files from disk and drawing a waveform in native plugins.
This still under development and all file and function names are subject to changing. I share it to see if there are other helpful features requested that I missed. Here’s what I have so far:
uint32_t Audio::get_block_size(): returns the current audio block size (16-512)
void notify_user(std::string_view message, int duration_ms) tells the GUI to pop up a message for the given duration. Non-blocking
Patch::mark_patch_modified() tells the patch manager that the patch file has been modified internally (e.g. if the user loads a different file in a sampler)
uint32_t System::total_memory(): returns the maximum bytes of memory set aside for modules (may vary from FW version to version, but is always the same number for a session)
uint32_t System::free_memory(): returns the bytes of memory available/free for modules to use.
uint32_t System::hardware_random(): returns an actual random number (not just pseudo-random) generated by cryptography hardware. Takes 2-3 times as long as System::random().
bool System::hardware_random_ready(): call this before calling hardware_random(). If it returns true then a new random number is available, otherwise the hardware has not generated a new value yet.
uint32_t System::random(): returns a pseudo-random number
System::get_ticks(): number of milliseconds since power on
System::delay_ms(uint32_t): block/pause for the given period.
Patch::patch_path(): returns the path to the currently playing patch file (WIP)
Patch::patch_volume(): returns the currently playing patch’s volume (WIP)
And dr_wav is now included in the API so you don’t have to include it in your project separately.
There are also some helper classes.
WavFileStream: helper class to stream .wav files from disk
StreamingWaveformDisplay: helper class to draw a waveform to a graphical display, when only one sample is known at a time (e.g. the file is being streamed). Useful for native plugins but can also be used in VCV plugins without much fuss.
BlockResampler: efficient block-based resampler
StreamResampler: efficient stream-based resampler (basically BlockResampler but with a block size of 1 sample). Useful when an AsyncThread is filling a buffer while the audio thread is concurrently reading from.
api-v2.1 also brings with it some changes to the directory structure of the plugin-sdk. Mainly – I got rid of the metamodule-core-interface and metamodule-rack-interface submodules!
The files formerly in these submodule are now just part of the metamodule-plugin-sdk repo, inside core-interface/ and rack-interface/ directories, respectively. This makes navigating git submodules a lot easier. Some other files/directories have moved (and I might move some more).
Last time thought was send/receive as multiple packets … similar to usb midi approach.
Also ability to send/receive via a particular midi device.
Again usb midi is a good modell, uses a single byte index and supports 16 devices.
Does also require a separate meta data callback to tell us/user name of device.
When will 2.1 be added? Is this next major release of firmware or a ‘point’ update ?
Oh yeah I think I could allow sysex as multiple packets. Would it be enough to just send the raw data starting with 0xF0 and ending with 0xF7? Then let the receiver decode that as it wishes.
Though.. I’m not quite sure how to deal with 0xF7 escaping or if that’s a thing we need to consider.
Yeah a way to choose from multiple interfaces is a future feature. Do we need to choose MIDI USB device (“cable”) ID as well? I rarely use MIDI in real-world so not sure if that’s commonly used or just part of the spec.
SDK v2.1 will add the features in the first post, and firmware v2.1 will be the first firmware based on that, though somewhat of a minor release for non-developers. For users, the new/exciting feature is the Sample Player module(s).
Edit: Thought about USB MIDI cables more… we could just add a byte to each frame which equals the first byte of the USB MIDI packet (cable ID and “CIN” code) that the corresponding raw MIDI packet is part of. Then the receiving module can choose to filter by USB cable ID or not, as it wishes.
Though there’s still the issue of how a user to picks which cable or device to use… We could have something in the Prefs or on USB connection to let you choose which device and cable to connect to. But then using multiple cables at the same time would not be supported. Hmm…
sure, this would work…0xF7 is not allowed in sysex data stream anyway.
that said, I wonder if it might be easier for you to just implement the messages in the same way as USB , as a fixed 4 byte transfer with the CIN incorporating message type and cable number - rather than ‘reinventing the wheel’.
p16, table 4-1.
obviously fixed 4 bytes, uses a bit more data, but makes it simple
(iirc, you are already doing something like this anyway_
yeah, the virtual cable number is exactly what I meant, and yeah, you need ‘another’ callback, which says CN 1 = “usb device name”, CN = “another usb device”
this will work well for usb midi routes/hubs etc.
yeah, for now, Im assuming all data goes to all (interested) modules, I guess at some point you could implement a ‘filter’ api, if you are concerned with trying to reduce data being sent around … but I guess most modules will quickly filter out what they dont want anyway.
if you implement usb hubs at some point, then it wont be quite as simple… as you wont simply be able to use cable number.
though in theory fine for up to 16 midi devices still. that said, usb hubs introduced many complications - so I think you can cross that bridge if/when you get to it
OK yeah, this is starting to make sense. Internally there are 4 byte packets that are copies of the USB packets, sent to all MIDI listeners. To store it in rack::midi::Message, the USB header byte can’t be in the bytes array or it’ll break existing rack modules, so it would be in an extra field. Modules that are interested could read it to get the cable number and filter if they wish. That’s the easy part.
Then we would need a USB device/cable chooser, similar to the async file browser. Similar to how you pass a callback to the async file browser and when the user selects a file it passes it the file path string. Here it would pass your callback the USB cable/device id that the user selected (and maybe a string with the device name in case you want to display it?)
Then, knowing that information, a module could properly filter USB cable IDs for the one the user requested be used. And we could add some simple helper functions for doing the filtering.
When we add hub support, this would tie into it nicely, I think.
yeah, I meant the 4 bytes protocol as the “internal representation”, then ofc, you can have a wrapper which looks like the rack::midi::message.
must admit Id only vaguely thought about how the device stuff would related to the rackapi. Id assumes something like CN could be deviceId, on rack::midi::Port/rack::midi::InputDevice etc
not thought about sysex with rack api - does it support sysex?
whilst I recognise it has variable bytes for its packet (iirc), this is not the only issue.
as on a serial line, the sysex does not necessarily come in one ‘burst’. you cannot wait for the last 0xf7 before sending.
e.g. an old trick for having a continuous data stream used to be to use a sysex stream ‘open’ (so never send 0xf7) , and just keeping sending data down it.
also ofc, sysex messages could get very very large, e.g sending firmware or complete synth preset dumps - not something you’d want to load into ram
… so I kind of assumed vcv didnt really handle these cases?!
but yeah, I can see the difficulty of having this represented within rack::midi::message.
I saw “supports Sysex” in a Rack CHANGELOG a while back, and some comments around rtmidi, but I don’t know how it handles cases like you describe with buffering indefinitely etc…
You’re developing a module to run on Rack and MetaModule, that uses MIDI, right? If there’s sysex running through Rack, then I guess we’ll find out eventually how it handles it.
I think the “streaming” method is good though, just send the packets as they arrive and let the module decide if it needs to process immediately or hold onto them.
yeah a while back I created a module that was talking to the Embodme Erae Touch using its SDK which is all sysex. that said, when I did this, I used rtmidi directly, not rack api - not sure sysex was supported at the time - so Id need to review this now, to see if its changed.
note: erae sdk is not continuous, I was just wondering if/how rack api would handle that , or if it was (simplistically) just reading the whole message into the buffer.
note 2: erae touch also has multiple midi ports, hence the interest in that.
but no rush Im playing with other modules at the moment