[ ]:
import time

import sc3nb as scn
from sc3nb import Synth
[ ]:
sc = scn.startup(with_blip=False)

Nodes (Synth and Group)

One of the most important objects in SuperCollider are Nodes.

To see all Nodes on the Server use

[ ]:
sc.server.dump_tree()

You can also get a Python object represenation of the current server state via.

[ ]:
root_node = sc.server.query_tree()
root_node

Note that this will send a /g_queryTree command and parse the /g_queryTree.reply response of the server. The resulting Group representation will currently not be updated unless you use query_tree again.

[ ]:
root_node.children[-1]

A node is either a Synth or a Group of nodes

Synth

Create and control a Synth

Create a new Synth

[ ]:
synth = Synth(name="s2", controls={"freq": 100})
synth

You can now hear the Synth playing and see it in the NodeTree

[ ]:
sc.server.default_group
[ ]:
sc.server.query_tree()

Free a synth

[ ]:
synth.free()

Start the synth again

[ ]:
synth.new()
[ ]:
synth.nodeid

Calling new on an already running synth will cause a SuperCollider Server error

[ ]:
synth.new()
time.sleep(0.5)  # wait some time to ensure the audio server has send the /fail OSC message

Pause a synth

[ ]:
synth.run(False)
[ ]:
synth

Run a paused synth

[ ]:
synth.run() # default flag for run is True

You can also wait for a Synth

[ ]:
synth_with_duration = scn.Synth("s1", dict(dur=2))
# wait for the Synth to finish playing
synth_with_duration.wait()

Set / get Synth parameters

Set synth parameters, using any for the following set calls

set(key, value, ...)
set(list_of_keys_and_values])
set(dict)
[ ]:
synth.set("freq", 200)
[ ]:
synth.set(["freq", 30, "amp", 0.3])
[ ]:
synth.set({"freq": 130, "amp": 0.1})

The Synth does save its current control arguments in current_controls

[ ]:
synth.current_controls

However these are only cached values on the python object. Updating them will only affect the new call. See below why this can be useful.

[ ]:
synth.current_controls['freq'] = 440
synth
[ ]:
synth.free()
synth.new() # The new Synth will be created with freq = 440

To see what arguments can be set you can look at the synth_desc

[ ]:
synth.synth_desc

You can use get to see the current value. This will request the current value from the SuperCollider audio server

[ ]:
synth.get("freq")
[ ]:
synth.get("pan")

This will also update the cached values in current_args

[ ]:
synth

This is also possible directly with

[ ]:
synth.pan

Which can also be used for setting the argument

[ ]:
synth.pan = -1

You can also query information about the Synth. Look at Group for more information about these values

[ ]:
synth.query()
[ ]:
synth.free()

Keep in mind that getting a synth value is always querying the server.

This means we need to receive a message which cant be done using the Bundler. If you want to use relative values in a Bundler you should use the cached values from current_args

[ ]:
with sc.server.bundler() as bundle:
    synth.new({"freq": 600})
    for _ in range(100):
        synth.set(['freq', synth.current_controls['freq'] * 0.99])
        bundle.wait(0.05)
    synth.free()
[ ]:
synth.wait() # wait for synth

Some methods also allow getting the OSC Message instead of sending

[ ]:
synth.new(return_msg=True)

Refer to the OSC communication example notebook if you want to learn more about messages and bundles.

Group

Nodes can be grouped and controlled together. This gives you mutliple advantages like

  • controlling multiple Synth together

  • specifying the execution order of the nodes. For more details look at Order of nodes

The SuperCollider audio server scsynth does have one root Group with the Node ID 0.

In this root group each user got an default group were all the Nodes of the user (Synths and Groups) should be located

[ ]:
sc.server.query_tree()

We have the following default Group

[ ]:
sc.server.default_group

Creating Groups

[ ]:
g0 = scn.Group()
g0
[ ]:
sc.server.dump_tree()
[ ]:
g0.free()
[ ]:
sc.server.dump_tree()
[ ]:
g0.new()
[ ]:
sc.server.query_tree()
[ ]:
sc.server.default_group

Create a Group in our new Group

[ ]:
g1 = scn.Group(target=g0)
g1
[ ]:
sc.server.query_tree()

You can get information about you Group via

[ ]:
g0.query()

The query contains the same information as a Synth query and additionally the head and tail Node of this group.

Notice the special value -1 meaning None.

[ ]:
g1.query()

Free the node

[ ]:
g0.free()
[ ]:
sc.server.query_tree()

Order of Nodes

The execution of the Nodes does plays an important role. For more details look at the SuperCollider Documentation

The Node placement of a Nodes can be controlled by the instantiation arguments

  • add_action - An AddAction that specifies where to put Node relative to the target

[ ]:
list(scn.AddAction)
  • target - The target of the AddAction

  • group - The group were the Node will be placed

Example of Ordering Nodes

[ ]:
g0 = scn.Group()
g0
[ ]:
g1 = scn.Group(target=g0)
g1
[ ]:
s0 = scn.Synth(target=g0, controls={"freq": 200})
[ ]:
s1 = scn.Synth(add_action=scn.AddAction.BEFORE, target=s0, controls={"freq": 600})
[ ]:
s2 = scn.Synth(add_action=scn.AddAction.TO_TAIL, target=g1, controls={"freq": 1200})
[ ]:
sc.server.query_tree()
[ ]:
s1.move(scn.AddAction.BEFORE, s2)
[ ]:
sc.server.query_tree()
[ ]:
g0
[ ]:
g0.run(False)
[ ]:
g0.run(True)
[ ]:
g1.run(False)
g1.query_tree()
[ ]:
s0.freq, s1.freq, s2.freq
[ ]:
g0.set("freq", 100)
s0.freq, s1.freq, s2.freq
[ ]:
sc.server.dump_tree()
[ ]:
g1.run(True)
[ ]:
sc.server.query_tree()
[ ]:
g1.set("freq", 440)

s0.freq, s1.freq, s2.freq
[ ]:
g0.query_tree()
[ ]:
g0.move_node_to_tail(s1)
[ ]:
g0.dump_tree()
[ ]:
g1.set("freq", 800)

s0.freq, s1.freq, s2.freq
[ ]:
sc.server.default_group.query_tree()
[ ]:
sc.server.free_all()
sc.server.dump_tree()
[ ]:
sc.exit()
[ ]: