The ULA ring is a simple, open, and extensible way to connect many parts of a system together. All Ula subpatches can send messages to each other on single-wire ring, and they can be any sequence on the ring. The Ula controller can display all ring communication and stops stack overflow due to endlessly cycling messages. All messages passing through the Ula controller may optionally be printed to the to the Max debug window with a timestamp. Because the controller receives all ring messages, it can also manage message recording and playback, to recreate any AV composition on the Ula system.
Ula v1.1 improves upon Ula 1.0 by providing more detailed messages, as well as more ways for patchers to talk to each other, such as message broadcast, snooping, and injection. The 1.1 controller, inside the Diag subpatch, also provides ring tests, reset, performance stats, and a simple way to add and remove subpatches from the ring.
The Ula ring system is more of a design methodology than a fixed design. Ula's Diag module is designed to be reusable, but the method described for ring communication could be used in many designs that don't embed the Ula components. So understanding why and how Ula works is generally useful, even if you don't plan to build in Ula yourself.
In Computer Aided Software Engineering (CASE), connections between multiple components can be more than any other part of large and complex software designs. Also, in Max/Msp patches, saving the container for poly~ and bpatcher objects in a different folder disconnects the containers for file externals, and if there are inlets and outlets, it is difficult to reconnect them.
Ula therefore uses a token ring to simplify inter-component communication and customization for particular needs. Because all messages pass on a single serial connection ring, any subpatcher can be anywhere on the ring and still communicate with all other subpatchers. As a result, the layout is also simple. The following example is for a Ula 1.1 design with two keyboards, a 16-channel polyvoice unit, arpeggiator, and three synthesizers. All the components talk to each other on a single-wire ring, which is easy to see, and easy to reconfigure.
The main objection to ring protocols, over point-to-point wiring, is the processing overhead for message header parsing. Rings are still more efficient that network hubs, which is why, interestingly enough, Apollo computer simplified the IBM X.25 packet switching protocol in the design of its Apollo Domain ring for the NASA space program. Historically, this is why packet-switched networks still refer to the message content with the rocket-science term, 'payload.'
Historically, the thought is, if there are two components which interact with each other, then point-to-point wiring is more sensible. As soon as three components interact, however, a ring protocol makes more sense than multiple point-to-point connections. In Max/MSP, more advanced designs want to share commands and data for many reasons, including preset recall/storage, reset coordination, clocking, recording/playback, and debugging. So, the first step up in interconnection for a straightforward point-to-point communications is a ring. Ula therefore defines the simplest possible generic protocol for ring messaging while still providing a full set of features for design and debugging.
That being said, if a particular message or command does not need of the shared functionality, it does not need to be put on a ring, just because it is between components on a ring network. The point of a ring protocol is to provide a way for components to share commands and data when they need to do so.
The magic is in the Diag module. It has its own panel controls which look like this.
To use it, all subpatchers on the ring send messages with a three-character prefix defining themselves and the destination. The messages pass around the ring to the Diag module, which checks the tag (printing an error to the console if the message has no destination or wasn't used by a source), then changes the prefix to a single character, indicating the message has been checked, and forwards the messages onto the ring again. The single-character prefix defines the message's destination. The other Ula modules simply check incoming messages for their own single-character prefix, consuming the message if it is for them, or otherwise passing the message unchanged to the next subpatcher on the ring.
Obviously, for a Ula module to send a message to another on the ring, it really only needs a destination tag. However, if a message is put on the ring which does not have the correct destination tag, it cycles endlessly around and around, eventually causing a stack overflow. The Diag module prevents this.
Because all messages pass through the Diag module, it can also provide other utilities, such as a unified point for message printing, recording, playback, reset management, preset management, timing control, and other global functions.
Again, obviously, the tags do not need to identify the source, only the destination. Ula 1.0 only defined the destination with a prefix so it would be parsed by the Diag module first.
During debugging, messages printed to the console window were then confusing, because it was not clear which subpatcher made them. Ula 1.1 therefore added the source tag to the protocol, providing printing of useful debug messages identifying the source and destination for all ring messages.
While this protocol may seem restrictive at first, once in place, it provides a number of ways for subpatchers to talk with each other, as well as Diag services. These include point-to-point messaging, broadcast, snooping and injection.
Perhaps the most common messages are ones with a single source and single destination. This simple example shows two subpatchers, A and B. Pressing the button in A sends a message to B, and pressing the button in B sends a message to A.
while there's a number of ways to inject messages into the ring, the best way is to define the source as the Diag module. These messages may be recorded, and may be printed to the debug window with a timestamp. This example sends a message to subpath B.
All modules on a ring can snooping messages for other modules. When snooping, other subpatchers listen for a message from one place to another, but don't remove it from the ring. this example shows patcher C snooping for a message to patcher B.
In Ula 1.1, snooping does have some minor restrictions. Point-to-point messages pass from the source to the Diag, then from Diag to the destination. Subpatchers which are snooping must therefore be after the source (snooping for the Diag prefix) or before the destination (snooping for the destination prefix). If the snooping module is between the source and the destination, it won't see the message pass by it on the ring.
The designer can also broadcast any message to all modules on the ring. Again, three's a number of ways to do this, but the best way is for the message simply to declare its subpatcher source as normal, and the destination as the Diag module. Then, the first time the message passes around the ring, it goes to the Diag module in the normal way. The Diag module removes the prefix and also sends the message in the normal way. In this case, though, the Diag moduole is sending the message to itself, so the message passes all the way around the ring until it reaches the Diag module again. The Diag module then displays a print message if set to do so, then consumes the message and removes it from the ring. Now, by sending the message to the Diag module, the source therefore ensures all subpatchers on the ring receive the message and so, all subpatchers can snoop the message.
In the following example, pressing the button broadcasts the message "broadcasting" from the C subpatch to all ring modules.
To receive the broadcastmodules need one additional letter and wire: a 'D' in the input router, and a wire from the router's 'D' outlet to the patch output to pass broadcast messages all around the ring.
Once a design is working, messages can traverse the Ula ring without Diag processing, within certain constraints. If sending a message downstream (without passing through Diag on the ring), a sender can simply use the target letter prefix for the message, so the message jumps straight to the target. The receiver then consumes the message before it reaches the Diag module.
However, sending a message upstream isn't possible without removing or editing the Diag module, unless a router path is introduced to hop the Diag module. Hopping isn't really that useful. Jumps may be useful. It's safe to assume, if you've read this far, you already understand enough of ring protocols not to need illustrative examples.
The Ula 1.1 Daig module is a patcher object. The layout provides the same access to the panel controls in editing and presentation modes (wires over the panel when in editing mode and locked):
Enabling printing puts messages in the Max console window. There are three debug levels available in the Diag panel: All, info only, messages only, and errors only. The debug level is saved with the Diag Preset.
When the Diag module receives a message is parses it and presents some information about the message. In the point-to-point example above, sending the message prints something like this:
|diag:||[01:37:24:335] Arp -> Beats: I'm a message from A|
|B_received_this||I'm a message from A|
|C-snooped-this||I'm a message from A|
...because Ula 1.1 by defualt recognizes the name 'A' for arpeggiators and 'B' for beat geenrators. The C subpatcher also snooped the message. Broadcasting a message from the module 'C' in the above example prints the following:
|ERROR:||[06:37:54:516] No source 'C' in the message: 'DC D broadcasting'.|
...because C is not one of the recognized patcher names in the Diag module. Adding the name C, with the description 'Clocker' and saving the daig module prints this:
|diag:||[06:37:55:108] Diag broadcasted for Clocker: 'broadcasting'.|
|B_received this:||[06:37:55:108] broadcasting|
|C_received this:||[06:37:55:108] broadcasting|
...because in the example patch, only modules B and C are set up to receive broadcasts.
The two text fields for the module abbreviations and full names are editable and saved with the preset. The abbreviations are the module letters (as used in the subpatch routers) and full names (to be printed in the Max window). After changing these lists, press the 'Save' button to push the values into all the internal logic and store the values in the preset.
The Ula system has a simple naming convention so that the destination tag is not more than one or two letters long. Note the only absolute requirement, by this naming methodology, is that the first letter is not the tag for another module. All predefined module tags are single characters, to simplify design, and uppercase, as most other Max messages are lowercase. Short names are faster to parse. This constitutes the entirety of the Ula ring protocol. Some names are suggested for specific modules to simplify sharing of modules between different designs. The Ula 1.0 convention is as follows.
|Acronym||Module Name (*Message Function)|
|A, A1, A2...||Arpeggiators|
|B, B1, B2...||Beat sequencers|
|F, F1, F2....||Effects units|
|I, I1, I2....||Inputs|
|K, K1, K2....||Keyboards|
|M, M1, M2...||Multichannel Controller and channel displays|
|O, O1, O2...||Outputs|
|P, P1, P2...||poly~ objects, pitch messages*|
|Q||Queue recall/storage (in Ula 1.3)|
|R||Reset and Preset signals*|
|U, U1, U2||User modules|
|V||Voice and preset messages*|
|W, W1, W2..||Wave generators (synth1, synth2, synth3...)|
|X, X1, X2...||Extensions|
While this is provides as a guidem, the point of the naming protocols is that, if no other modules use these specific letters at the beginning of messages, the Diag module can always catch messages which are not consumed by other modules and remove them from the ring to stop stack overflow. It is not intended that the protocol enforce specific design practices, however. If it improves design readability to use longer names, then it's certainly possible to do so. There will be a very, very slight increase in ring processing to handle the longer names.
Save. The 'Save' button stores the current Diag panel settings as a preset in the design.
Reset. Pressing 'Reset,' in v1.1, broadcasts 'R 0' to all modules. The module also broadcasts 'R 0' after it is initialized. Please see Ula 1.2 for more comprehensive reset and preset capabilities.
Stats. The stats checkbox enables printing of CPU usage to the Max window periodically. Its setting is saved with the design.
Ringtest. pressing this button broadcasts a test message 25 times then reports the fraction of milliseconds that the test took. If there is no performance report after pressing this button, there is a discontinuity in the ring which is causing Diag messages not to return to the Diag module.
Message Injection from Diag. Multiple messages can be sent at once from the injection text field in the Diag module. To do so, separate the messages by commas, then press the 'Send to Ula' button to inject the messages into the ring. The messages are injected at Diag's back end, that is, all the messages pass around the entire ring once before they are checked by the Diag module's message parser. The text entry field for injecting messages is stored in the preset.
- 11/8/2013: Ula 1.1 documented and released.
- 10/30/2013: Ula 1.0, concept version, shared on Cycling'74 website.
Versions after Ula 1.1 also provide global services for clocking, performance monitoring, saving and storing presets, and real-time recording and playback.