Rubyk is a tool to process signals. It is made of two different agents:
The planets handle all the heavy computation and works in real time. Their worlds are made of many nodes connected together through links coming in and out of the nodes through slots. These slots are called outlets when they correspond to an outgoing signal and inlets for incoming signals. There is also a special outgoing slot called notifier that is used to notify observers of the node’s state changes.
The satellites correspond to user interfaces used to observe the data flow and alter the nodes and connections. All planets and satellites can live on different machines, networks and hardware.
There can be many satellites observing and altering the same planets.
This is a draft… It will need a rewrite after zeroconf implementation.
For the moment, the basic idea is that a satellite registers through
/reply_to host,port
And the answers to all queries are sent to all satellites (possibly through multicast).
Communication between nodes on the same planet is done through direct function calls by slots, either using the virtual method bang
for first inlet or a function pointer (functor) for other inlets. Signals are passed by reference.
Communication between nodes from different planets is done by sending open sound control messages through UDP packets. These packets land into the osc port of the server with the same priority as observers (low).
Signal spying is implemented in special nodes that connect to outlets and send all the data through UDP directly to the observer. With every packet, the planet sends the current TTL (time to live) of the spy node. If the app does not update the TTL by sending the observe command before it reaches zero, the spy node is deleted.
Planets contain nodes connected together to process signal. They must be able to respond to node queries (node listing, node method listing) :
A very simple planet could abstract all it’s processing through a unique node:
/pluto/mic/amp /pluto/mic/amp 0.88 /pluto/mic/out/audio/link /venus/amp/in/sig1 /pluto/mic/notify /me
If a planet can instantiate new nodes, it should contain the “rubyk” meta node:
/venus/rubyk -- get rubyk capabilities /venus/rubyk/class/Metro/new 'met' -- create Metro instance /venus/rubyk/free 'met' -- delete node
Links between nodes are created/destroyed by the nodes themselves:
/venus/met/out -- get all outlets /venus/met/out/tic -- get all links /venus/met/out/tic/link /venus/met/bang -- link /venus/met/out/tic/unlink * -- remove all links
Discovery of planets is done via ZeroConf (see avahi).
In order to discover what a planet contains the satellite sends messages ending with a /
(slash). For example, this could be the queries used to discover a planet called “venus” containing a metronome called “metro” and a counter called “c” :
query : /#list answer: rubyk/,metro/,c/
query : /venus/metro/#list answer: class,bang,tempo,start,stop,in/,out/
It contains methods “class”, “bang”, “tempo”, “start” and “stop” and the sub-objects “in/” and “out/”. See how the absence of a trailing slash indicates a method.
query : /venus/metro/tempo answer: 115
query : /venus/metro/tempo 90 answer: 90
query : /venus/metro/tempo -100 answer: 90
The reply sends the value of the parameter after the action (unchanged in case of error).
query : /venus/metro/tempo/#info answer: "Tempo value in beats per minute. A value of '0' halts the metronome."
All nodes on a planet are subclasses of Node
. Even the planet
itself is a subclass of Node
.
See Group for information on how to group objects on a planet.
Any object loaded from the library is instanciated as a Class. This object contains information on the number of outlets/inlets, the class name, the super class.
The Object stores information like:
Moving an object around is done through “set_parent” or “set_name”.
Objects send Values
to each other, through outlets and inlets but also by direct calls to accessors (/met/tempo
returns a Value
for example).
The Value
can be either a direct value (double) or a smart pointer to a more complex data container.
Here is a graphical definition of Value
:
Value
.Value
type is an anonymous holder for Matrix,Midi,String, etc.
const Value v1(new NumberData(1)); // v1.refCount = 1 Value v2(new Data()); // v2.refCount = 1 Value v3 = v1; // v1.refCount = 2 v2 = v1; // v2 destroyed, v1.refCount = 3 Number n; // n.refCount = 1, n is a NilValue if (!v3.set(&n)) { // v1.refCount = 4, n is now a NumberValue return -1; } printf("%i\n",n->value()); // v1.refCount = 4: const accessor "->" n.mutable_data()->increment(); // v1.refCount = 3, n.refCount = 1 (clone) if (v1.set(&d)) { // type conversion to native types printf("%f ... ok\n", d); // v1.refCount = 1 }
Life starts in the bang
method where all the processing is triggered. During processing, the node can send signals through it’s outlets. It should first send signals with the highest outlet id (right to left output).
Once the processing is done, if the state changes the node should call it’s changed
method which registers this node as wanting observer notifications.
At the end of the planet’s processing loop, notifications are sent, eventually from a slow thread. Going through each of the nodes registered as needing observer notification, the notify slot is called. This slot calls the node’s state
method to get a dictionary with the node’s attributes and sends the attributes to the observers.
When an observer wants to be notified about a node’s state, these are the steps involved:
1. get the node’s class
/venus/met/class --> Metro
2. get the interface for this node (views/Metro)
3. instantiate the widget with the node’s url
/venus/met
4. interface sends notification need to it’s node depending on its visibility status (it sends a list of attribute keys needed for its state update):
/venus/met/notify /satellite/met "frequency, osc_type, ..."
When an exceptions is raised, the node becomes freezed (locked). It will no longer process signals. The only way to unlock a node is to initialize it again (send it new parameters).
Exceptions should never occur when receiving signals. Bad/incompatible signals should be simply rejected.
Funding from the Swiss Federal Office of Culture to write the graphical frontend to rubyk !
Moving from a global mutex to a global select/poll loop.