Connection overview:
Filter:
Connection/Guides (extension) | Connection

Connection overview
ExtensionExtension

An overview of the Connection quark

Description

Connection provides a more convient, expressive, and powerful way of connecting and synchronizing objects, values, and UI views in SuperCollider. It is built on top of (and is compatible with) the existing Object: addDependant, Object: removeDependant Object: changed etc. patterns, but provides greatly expanded functionality.

NOTE: While Connection has some convenient methods for operating on collections of objects or methods, it is not an "automatic gui creation" library - there are much better tools for this. Instead, it attempts to provide semantically clear operations that can be composed in obvious ways to achieve what you want. In general, individual lines of code using Connection classes and methods express obvious relationships between objects, with little-to-no "magic" or behind-the-scenes inference of intent.
WARNING: The Connection quark is under active development. Most API's, object names, etc. are subject to change or removal. Please send feedback, bug reports, feature ideas to Scott Carver <scott@artificia.org>.

Introduction

SuperCollider has a built-in notification pattern that allows any object to be notified if another object changes:

Any dependant of ~notifier receives a call to it's update method with the arguments (object, changed ...args). In this example, ~notifier.changed(\foo, "bar") results in ~recipient.update(~notifier, \foo, "bar"). The "changed" argument is usually used to express what changed about the object, and the argument after is usually the new value or a description of that change.

This pattern is simple and powerful, but has pitfalls. When connecting up more complex objects - for example, a UI View with multiple sliders and fields - this pattern requires an extensive amount of bookkeeping. You will potentially need to keep track of:

  1. Every object you've added a dependant to.
  2. Every object you've added as a dependant.
  3. Which objects you've added as dependants to which other objects.

And, it becomes verbose as you begin to deal with multiple signals interconnecting multiple objects. Often this takes the form of a proliferation of inline functions that simply forward updates with minor changes:

This is a dangerous anti-pattern: once you've added an untracked function using "this", you have effectively leaked the "this" object until you call object.release to clear it's dependants (and - if others are listening to object, you'll disconnect them too!). If you fail to disconnect all of your object → dependant connections, you risk creating memory leaks or continuing to send updates to objects that are no longer in use.

The Connection class

The core of the Connection quark is the Connection class, which encapsulates the connection between one object and one dependant:

Once you have created a Connection, you can manipulate that connection generically, without needing to store or care about the specific objects it connects. It handles addDependant / removeDependant calls, and is guaranteed not to leak memory after it's disconnected and is not reachable. This means significantly less bookkeeping for more complex sets of connections.

The canonical way to create connections is with the Object: -connectTo method - this is preferred over creating Connections directly.

This is not much more efficient than simply using addDependant, and we still have lots of trivial inline functions.

Connecting objects expressively

If we want to add 20 sliders, our code looks very similar.

The Connection quark provides several conveniences that make the above example more expressive and straightforward.

.signal()

For our Sliders, slider.signal(\value) will forward only things that match slider.changed(\value,...). In addition, Connection is smart enough to automatically add a slider.action function to turn slider actions into slider.changed(\value, slider.value)

ConnectionList

When managing groups of connections, we can use ConnectionList. ConnectionList is a standard collection, but forwards all common Connection methods on to each item it contains. So, rather than iterating over a collection of Connections to free them all, you can simply use connectionList.free().

ConnectionList: *make will collect all Connections that are created during the execution of a function.

Connecting multiple objects

The Connection: -connectTo method can take multiple objects, and will connect all of them. This makes it easy to create a one-to-many connection. For example: slider.connectAll(a, b, c).

The Collection: -connectAll method provides a many-to-one connection, connecting all objects in a list to one dependant. For example: ~sliders.connectAll({ |slider| "Slider changed: %".format(slider.value).postln; })

If we want to add a NumberBox for each of our sliders, we need a many-to-many connection. We can use Collection: -connectEach to connect a list of objects to a list of dependants. For example:

You may notice that, for connectEach, we provide some other arguments. The form of connectEach is objectList.connectEach(signalFunction, dependentList, slotFunction), which is equivalent to:

In short, the two functions should take list items and provide an appropriate object or dependent to be connected. If you specify a Symbol or a String instead, these are taken to be arguments to the objects .signal or .methodSlot methods. So, the following are equivalent:

See Slots below for more information on Object: -methodSlot.

MVC and ControlValue's

You can see that the number of connections we have is rapidly increasing. The model-view-controller design pattern suggests that we keep values in a single place (the model), and notify objects that display or use those values (views) when a value changes. Connection provides a ControlValue class to store values, and transmit changes when the value changes. We can use NumericControlValue to store our numeric values.

Note that connections are one-way - we want our sliders to be updated when our values change, but also change the values when we move them. So, we need a connection in each direction. We only care about our number boxes displaying values, so we can make those one-way. We can see this two-way connection in action if we animate our values:

Slots

The valueSlot method above is a specific case of the more general methodSlot, which allows you to forward updates to a specific method of an object. This functionality is provided by MethodSlot, which you generally create via object.methodSlot("methodName").

The methodSlot method can specify both a method name and the order of the arguments passed. Specifying a string argument in methodSlot is exactly equivalent to inserting that string into a function the following form (under the hood, the quark does exactly this):

So, for example, when a signal like object.changed(\quarter, 2) is recieved by dependant.methodSlot("updateElement(changed, value)"), the resulting call is dependant.updateElement(\quarter, 2).

Synths and Groups have slot methods for their arguments, making it easy to map values to Synth parameters:

Control values

Of course, our sliders only range from 0..1. And, we lose their values if we close the View. The NumericControlValue class, which provides a model for a numeric value, broadcasts updates when it changes, and can be connected to other objects that are interested in it's value. This class is almost identical in functionality to the CV class from the Conductor quark.

We could have been more succinct when connecting ~synth arguments by using argsSlots, which returns a collection of slots for a argument list of names.

Connection modifiers

Connection has modifier methods that allow you to express useful ways of handling or modifying updates between objects. Connection modifiers can be applied in three ways.

  1. They can be used as a method of objects returned by the .signal method. object.signal(\value).filter(...).connectTo(dependant)
  2. They can be used as a method modifying an existing connection. object.signal(\value).connectTo(dependant).filter(...)
  3. They can also be constructed as objects and connected in a chain with objects. object.signal(\value).connectTo(UpdateFilter(...)).connectTo(dependant);

filter

Filter will only allow updates through that match either a specified \key, or for which a function returns true:

transform

Transform will use a provided function to modify the values being passed in an update. The result of the function must be of the form: [object, changed, args], where args is an array. Returning nil will not forward the update, effectively filtering it.

defer

The equivalent to Function: defer - will defer updates for a specified amount of time, and/or to a different thread.

collapse

This will collapse updates over a specified interval, effectively rate-limiting and ensuring only one update (the most recent) is applied for a period of time.

oneShot

This will disconnect or free a connection after it is fired. Note that disconnected connections can be reconnected (they will again auto-disconnect the next time they are fired). Free'd connections will free resources and cannot be reconnected. Freeing one-shot connections is an effective way to avoid holding on to these connections yourself.

Debugging

Updates happening to connected objects can be traced using Connection: trace. Traces of connected items are shown with "⋯⋯". Traces of disconnected items are shown with "⋰⋰".