Fundamentals
Export Targets
Code Export
Patcher UI
Special Topics
RNBO Raspberry Pi OSCQuery Runner
The RNBO Raspberry Pi OSCQuery Runner
The OSCQuery Runner is the simplest method to load exported code onto a Raspberry Pi.
It is setup to provide plug and play access to the pi from the Max application and provides a communication with the running code using OSCQuery. When a RNBO patcher is exported to an rPi the OSC namespace is automatically generated based on the parameters and inport/outport objects in the RNBO patcher code.
You can communicate with the runner via Open Sound Control (OSC) over either websockets or UDP.
Communicating with the runner Via UDP
If you have sucessfully connected to an rPi with an active runner in Max, the associated target sidebar info should show you the UDP and HTTP/WS host port and, for OSC, transport.
By default the HTTP and websocket port are 5678
and OSC is UDP at 1234
so if you know the IP address of your runner, you should be able to load a webpage with the url:
http://<ipoftherunner>:5678
and send OSC messages at osc.udp://<ipoftherunner>:1234
The device hostname followed by port can also be used. i.e., if the hostname is c74rpi, then the addresses are http://c74rpi.local:5678
and osc.udp://c74rpi.local:1234
.
The websocket interface is created via an http upgrade from the HTTP host and port.
*NOTE* the websocket interface is used for more than just `OSC`, so you'll want to detect the type of the websocket messages and only try to parse the Binary messages.
OSC Namespace
If you've exported a patch to your Pi, you should be able to investigate the runner's OSCQuery namespace via HTTP, as described above. For instance, if the runner is at the default name c74rpi.local
, loading the URL http://c74rpi.local:5678
will reveal a json file exposing the full paths of RNBO code running on the Raspberry Pi.
FULL_PATH
entries correspond to OSC
addresses. TYPE
entries identify the OSC
type that those parameters expect, if any.
If the ACCESS
value is 2 (set only) or 3 (get set) then you can send OSC messages to that address to alter parameters. ACCESS
1 is reserved for system/read-only data values.
See the OSCQueryProposal for more details on OSCQuery.
Below an example of what the json tree structure will look like will look like using the exported RNBO code from the RNBO OSCQUERY example.
{
"FULL_PATH": "/rnbo",
"CONTENTS": {
"info": {
"FULL_PATH": "/rnbo/info",
"DESCRIPTION": "information about RNBO and the running system",
"CONTENTS": {
"version": {
"FULL_PATH": "/rnbo/info/version",
"TYPE": "s",
"VALUE": "0.14.0-dev.12",
"ACCESS": 1,
"CLIPMODE": "none"
},
"system_name": {
"FULL_PATH": "/rnbo/info/system_name",
"TYPE": "s",
"VALUE": "Linux",
"ACCESS": 1,
"CLIPMODE": "none"
},
"system_processor": {
"FULL_PATH": "/rnbo/info/system_processor",
"TYPE": "s",
"VALUE": "armv7",
"ACCESS": 1,
"CLIPMODE": "none"
},
"system_id": {
"FULL_PATH": "/rnbo/info/system_id",
"TYPE": "s",
"VALUE": "e76c9ab1-efbf-404b-87ef-30ba0021fab9",
"ACCESS": 1,
"CLIPMODE": "none",
"DESCRIPTION": "a unique, one time generated id for this system"
},
"disk_bytes_available": {
"FULL_PATH": "/rnbo/info/disk_bytes_available",
"TYPE": "s",
"VALUE": "26440867840",
"ACCESS": 1,
"CLIPMODE": "none"
},
"update": {
"FULL_PATH": "/rnbo/info/update",
"DESCRIPTION": "Self upgrade/downgrade",
"CONTENTS": {
"state": {
"FULL_PATH": "/rnbo/info/update/state",
"TYPE": "s",
"VALUE": "idle",
"RANGE": [
{
"VALS": [
"idle",
"active",
"failed"
]
}
],
"ACCESS": 1,
"CLIPMODE": "both",
"DESCRIPTION": "Update state"
},
"status": {
"FULL_PATH": "/rnbo/info/update/status",
"TYPE": "s",
"VALUE": "waiting",
"ACCESS": 1,
"CLIPMODE": "none",
"DESCRIPTION": "Latest update status"
},
"outdated": {
"FULL_PATH": "/rnbo/info/update/outdated",
"TYPE": "i",
"VALUE": -1,
"ACCESS": 1,
"CLIPMODE": "none",
"DESCRIPTION": "Number of outdated packages detected on the system"
},
"supported": {
"FULL_PATH": "/rnbo/info/update/supported",
"TYPE": "T",
"VALUE": null,
"ACCESS": 1,
"CLIPMODE": "none",
"DESCRIPTION": "Does this runner support remote upgrade/downgrade"
}
}
}
}
},
"cmd": {
"FULL_PATH": "/rnbo/cmd",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "command handler"
},
"resp": {
"FULL_PATH": "/rnbo/resp",
"TYPE": "s",
"VALUE": "{\"id\":\"40b2464b-0692-4ca7-86e9-4e8b080cf401\",\"jsonrpc\":\"2.0\",\"result\":{\"code\":2,\"message\":\"loaded\",\"progress\":100}}",
"ACCESS": 1,
"CLIPMODE": "none",
"DESCRIPTION": "command response"
},
"listeners": {
"FULL_PATH": "/rnbo/listeners",
"CONTENTS": {
"entries": {
"FULL_PATH": "/rnbo/listeners/entries",
"TYPE": "",
"VALUE": [],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list",
"DESCRIPTION": "list of OSC listeners"
},
"add": {
"FULL_PATH": "/rnbo/listeners/add",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "add OSC UDP listener: \"ip:port\""
},
"del": {
"FULL_PATH": "/rnbo/listeners/del",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "delete OSC UDP listener: \"ip:port\""
},
"clear": {
"FULL_PATH": "/rnbo/listeners/clear",
"TYPE": "I",
"VALUE": null,
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "clear all OSC UDP listeners"
}
}
},
"jack": {
"FULL_PATH": "/rnbo/jack",
"CONTENTS": {
"info": {
"FULL_PATH": "/rnbo/jack/info",
"CONTENTS": {
"alsa_cards": {
"FULL_PATH": "/rnbo/jack/info/alsa_cards",
"CONTENTS": {
"hw:pisound": {
"FULL_PATH": "/rnbo/jack/info/alsa_cards/hw:pisound",
"TYPE": "s",
"VALUE": "pisound - pisound\npisound",
"ACCESS": 1,
"CLIPMODE": "none"
},
"hw:1": {
"FULL_PATH": "/rnbo/jack/info/alsa_cards/hw:1",
"TYPE": "s",
"VALUE": "pisound - pisound\npisound",
"ACCESS": 1,
"CLIPMODE": "none"
}
}
},
"is_realtime": {
"FULL_PATH": "/rnbo/jack/info/is_realtime",
"TYPE": "T",
"VALUE": null,
"ACCESS": 1,
"CLIPMODE": "none",
"DESCRIPTION": "indicates if jack is running in realtime mode or not"
}
}
},
"config": {
"FULL_PATH": "/rnbo/jack/config",
"DESCRIPTION": "Jack configuration parameters",
"CONTENTS": {
"card": {
"FULL_PATH": "/rnbo/jack/config/card",
"TYPE": "s",
"VALUE": "hw:1",
"RANGE": [
{
"VALS": [
"hw:pisound",
"hw:1"
]
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"DESCRIPTION": "ALSA device name"
},
"num_periods": {
"FULL_PATH": "/rnbo/jack/config/num_periods",
"TYPE": "i",
"VALUE": 2,
"RANGE": [
{
"VALS": [
1,
2,
3,
4
]
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"DESCRIPTION": "Number of periods of playback latency"
},
"period_frames": {
"FULL_PATH": "/rnbo/jack/config/period_frames",
"TYPE": "i",
"VALUE": 256,
"RANGE": [
{
"VALS": [
32,
64,
128,
256,
512,
1024
]
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"DESCRIPTION": "Frames per period"
},
"sample_rate": {
"FULL_PATH": "/rnbo/jack/config/sample_rate",
"TYPE": "f",
"VALUE": 48000,
"RANGE": [
{
"MIN": 22050
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"DESCRIPTION": "Sample rate"
}
}
},
"active": {
"FULL_PATH": "/rnbo/jack/active",
"TYPE": "T",
"VALUE": null,
"ACCESS": 3,
"CLIPMODE": "none"
},
"transport": {
"FULL_PATH": "/rnbo/jack/transport",
"CONTENTS": {
"bpm": {
"FULL_PATH": "/rnbo/jack/transport/bpm",
"TYPE": "f",
"VALUE": 100,
"ACCESS": 3,
"CLIPMODE": "none"
},
"rolling": {
"FULL_PATH": "/rnbo/jack/transport/rolling",
"TYPE": "F",
"VALUE": null,
"ACCESS": 3,
"CLIPMODE": "none"
}
}
}
}
},
"inst": {
"FULL_PATH": "/rnbo/inst",
"DESCRIPTION": "code export instances",
"CONTENTS": {
"0": {
"FULL_PATH": "/rnbo/inst/0",
"CONTENTS": {
"jack": {
"FULL_PATH": "/rnbo/inst/0/jack",
"CONTENTS": {
"audio_ins": {
"FULL_PATH": "/rnbo/inst/0/jack/audio_ins",
"TYPE": "",
"VALUE": [],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
},
"audio_outs": {
"FULL_PATH": "/rnbo/inst/0/jack/audio_outs",
"TYPE": "ss",
"VALUE": [
"rnbo0:out1",
"rnbo0:out2"
],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
},
"midi_ins": {
"FULL_PATH": "/rnbo/inst/0/jack/midi_ins",
"TYPE": "s",
"VALUE": [
"rnbo0:midiin1"
],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
},
"midi_outs": {
"FULL_PATH": "/rnbo/inst/0/jack/midi_outs",
"TYPE": "s",
"VALUE": [
"rnbo0:midiout1"
],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
}
}
},
"params": {
"FULL_PATH": "/rnbo/inst/0/params",
"DESCRIPTION": "Parameter get/set",
"CONTENTS": {
"frequency": {
"FULL_PATH": "/rnbo/inst/0/params/frequency",
"TYPE": "f",
"VALUE": 120,
"RANGE": [
{
"MIN": 0,
"MAX": 1200
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"CONTENTS": {
"normalized": {
"FULL_PATH": "/rnbo/inst/0/params/frequency/normalized",
"TYPE": "f",
"VALUE": 0.10000000149011612,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both"
}
}
},
"cyclegain": {
"FULL_PATH": "/rnbo/inst/0/params/cyclegain",
"TYPE": "f",
"VALUE": 0.10000000149011612,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"CONTENTS": {
"normalized": {
"FULL_PATH": "/rnbo/inst/0/params/cyclegain/normalized",
"TYPE": "f",
"VALUE": 0.10000000149011612,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both"
}
}
},
"sahgainin": {
"FULL_PATH": "/rnbo/inst/0/params/sahgainin",
"TYPE": "f",
"VALUE": 0,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"CONTENTS": {
"normalized": {
"FULL_PATH": "/rnbo/inst/0/params/sahgainin/normalized",
"TYPE": "f",
"VALUE": 0,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both"
}
}
},
"groovegain": {
"FULL_PATH": "/rnbo/inst/0/params/groovegain",
"TYPE": "f",
"VALUE": 0.10000000149011612,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"CONTENTS": {
"normalized": {
"FULL_PATH": "/rnbo/inst/0/params/groovegain/normalized",
"TYPE": "f",
"VALUE": 0.10000000149011612,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both"
}
}
},
"phasorfreq": {
"FULL_PATH": "/rnbo/inst/0/params/phasorfreq",
"TYPE": "f",
"VALUE": 0,
"RANGE": [
{
"MIN": 0,
"MAX": 100
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"CONTENTS": {
"normalized": {
"FULL_PATH": "/rnbo/inst/0/params/phasorfreq/normalized",
"TYPE": "f",
"VALUE": 0,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both"
}
}
},
"phasorgain": {
"FULL_PATH": "/rnbo/inst/0/params/phasorgain",
"TYPE": "f",
"VALUE": 0,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both",
"CONTENTS": {
"normalized": {
"FULL_PATH": "/rnbo/inst/0/params/phasorgain/normalized",
"TYPE": "f",
"VALUE": 0,
"RANGE": [
{
"MIN": 0,
"MAX": 1
}
],
"ACCESS": 3,
"CLIPMODE": "both"
}
}
}
}
},
"data_refs": {
"FULL_PATH": "/rnbo/inst/0/data_refs",
"CONTENTS": {
"bufferone": {
"FULL_PATH": "/rnbo/inst/0/data_refs/bufferone",
"TYPE": "s",
"VALUE": "drumLoop.aif",
"ACCESS": 3,
"CLIPMODE": "none"
}
}
},
"presets": {
"FULL_PATH": "/rnbo/inst/0/presets",
"CONTENTS": {
"entries": {
"FULL_PATH": "/rnbo/inst/0/presets/entries",
"TYPE": "sss",
"VALUE": [
"60 freq",
"120 freq",
"440 freq"
],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list",
"DESCRIPTION": "A list of presets that can be loaded"
},
"save": {
"FULL_PATH": "/rnbo/inst/0/presets/save",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "Save the current settings as a preset with the given name"
},
"load": {
"FULL_PATH": "/rnbo/inst/0/presets/load",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "Load a preset with the given name"
},
"delete": {
"FULL_PATH": "/rnbo/inst/0/presets/delete",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "Delete a preset with the given name"
},
"del": {
"FULL_PATH": "/rnbo/inst/0/presets/del",
"TYPE": "s",
"VALUE": "",
"ACCESS": 2,
"CLIPMODE": "none",
"DESCRIPTION": "Delete a preset with the given name"
},
"initial": {
"FULL_PATH": "/rnbo/inst/0/presets/initial",
"TYPE": "s",
"VALUE": "",
"ACCESS": 3,
"CLIPMODE": "none",
"DESCRIPTION": "Indicate a preset, by name, that should be loaded every time this patch is reloaded. Set to an empty string to load the last loaded preset instead"
}
}
},
"messages": {
"FULL_PATH": "/rnbo/inst/0/messages",
"CONTENTS": {
"in": {
"FULL_PATH": "/rnbo/inst/0/messages/in",
"CONTENTS": {
"phasorinput": {
"FULL_PATH": "/rnbo/inst/0/messages/in/phasorinput",
"TYPE": "",
"VALUE": [],
"ACCESS": 2,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
},
"trigger": {
"FULL_PATH": "/rnbo/inst/0/messages/in/trigger",
"TYPE": "",
"VALUE": [],
"ACCESS": 2,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
},
"threshold": {
"FULL_PATH": "/rnbo/inst/0/messages/in/threshold",
"TYPE": "",
"VALUE": [],
"ACCESS": 2,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
}
}
},
"out": {
"FULL_PATH": "/rnbo/inst/0/messages/out",
"CONTENTS": {
"sahgainout": {
"FULL_PATH": "/rnbo/inst/0/messages/out/sahgainout",
"TYPE": "f",
"VALUE": [
0
],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list"
}
}
}
}
},
"midi": {
"FULL_PATH": "/rnbo/inst/0/midi",
"CONTENTS": {
"in": {
"FULL_PATH": "/rnbo/inst/0/midi/in",
"TYPE": "",
"VALUE": [],
"ACCESS": 2,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list",
"DESCRIPTION": "midi events in to your RNBO patch"
},
"out": {
"FULL_PATH": "/rnbo/inst/0/midi/out",
"TYPE": "",
"VALUE": [],
"ACCESS": 1,
"CLIPMODE": "none",
"EXTENDED_TYPE": "list",
"DESCRIPTION": "midi events out of your RNBO patch"
}
}
}
}
}
}
}
}
}
```
Inst
RNBO code export instances are listed at /rnbo/inst
and describe the state of the running patcher on the target. This path contains paths to set and/or get params
, data_refs
, presets
, midi
events and messages
to and from inports and outports. The jack
sub-path containst read-only access to the jack port name mappings for audio and midi io.
Listener Ports
Listener ports can be created on the runner to enable parameter changes to be communicated via UDP to other devices, looped back to scripts and applications on the same device, or both. To add a listener port, send a message via UDP to /rnbo/listeners/add
followed by the ipv4 address:port number
. This will add the numbered port to the runner on the Pi where changes can be observed in real time, without needing to poll the runner.
To delete a listener port, send a message via UDP to /rnbo/listeners/del
followed by the ipv4 address:port number
of the active listener port to remove.
A list of active listener ports can be queried via the address /rnbo/listeners/entries
and all listener ports can be cleared at once via /rnbo/listeners/clear
.
Audio Configuration
The audio settings of the runner such as the audio device, samplerate, number of periods of latency and number of frames per period can be queried and set via /rnbo/jack/config
. To apply the changes after setting any of these values, send a 0
message to /rnbo/jack/active
shortly before sending a 1
to the same address.
Commandline OSC message to the Raspberry Pi
OSC messages can be sent to parameters from a commandline on the Raspberry Pi running rnbo code.
On Mac, to send a normalized parameter update to the Pi named c74rpi.local
we use oscsend
, which can be installed via homebrew.
Commands can be sent to control any parameter in the namespace:
oscsend osc.udp://c74rpi.local:1234 /rnbo/inst/0/params/foo/normalized f 0.2
In the case above, if foo
is a valid parameter in your loaded patch, and you send the above message, then load http://c74rpi.local:5678/rnbo/inst/0/params/
in a webbrowser, you should see that both foo
and foo/normalized
have been updated.
Commandline OSC Example for Windows
On Windows, you will need to install from the zip file which can be found at https://github.com/yoggy/sendosc/blob/master/README.md.
On windows, the command structure is like this:
cd <path to exe>/sendosc c74rpi.local 1234 /rnbo/inst/0/params/foo/normalized f .2
Again, ff foo
is a valid parameter in your loaded patch, and you send the above message, then load http://c74rpi.local:5678/rnbo/inst/0/params/
in a webbrowser, you should see that both foo
and foo/normalized
have been updated.
Communicating with the Runner via UDP in Max
The udpsend and udpreceive objects in Max can also be used to communicate with the runner in the same way.
See the Raspberry Pi OSCQuery Max example to learn more.
Communicating with the Runner Via Websocket
To run the Websocket example, the ws package is required. to install, we can run `npm install ws` from a commandline.
Basic Javascript Websocket Example
const OSC = require("osc");
{
let ws = new WebSocket(YOUR_RUNNER_URL);
ws.on('message', (d) => {
//must be a buffer because there are other non OSC websocket messages as well
if (Buffer.isBuffer(d)) {
try {
const msg = OSC.readPacket(d, {metadata: true});
//process
} catch (e) {
}
}
});
ws.on('open', () => {
//send OSC
const array = OSC.writePacket({
address: "/rnbo/inst/0/params/foo",
args: [
{
type: "f",
value: 1.0
}
]
},
{ metadata: true });
ws.send(array);
});
}
Commands
The OSCQuery runner uses a modified jsonRPC for command communication.
modifications:
* id
is a uuid.
* method calls may have multiple responces indicating progress.
Notes/Links
* osc.js