Warning

Under construction.

Synthesis definitions

Synthesis definitions (synthdefs) are the specification of sound synthesis or processing algorithms based on connecting unit generators (ugens). The latter are C/C++ efficient algorithms that perform specific tasks, such as oscillator, sound capture, playback and so on, they are pre-compiled as plugins (dynamic libraries) and can’t be modified. However, they can be combined to create higher level signal processing algorithms that perform more complex tasks and this is done by creating synthdefs.

When a synthdef is defined using the language no object is created in the server but a blueprint with the instructions to create that object that will be sent to the server which in turn will use it to create synthesis nodes. Synthesis nodes can be seen as some kind of higher level sound generators instances. Synthesis definitions are also non modifiable once the blueprint is in the server, although they can be overwritten.

So far, ugens and synthdefs are static elements, in the sense that they can’t be modified once created. On the other hand synthesis nodes can be created and patched dynamically as discussed below.

A synthdef is created from a Python function definition that contains interconnected ugen objects. To create the instructions to be sent to the server (the blueprint) the library analyzes the tree formed by the ugens and their parameters from an output ugen, technically any ugen that has side effects, and does some introspection to the function as we will see later.

The following example show the process of creating and sending a synthdef.

def sine():
    freq = 440
    amp = 0.1
    sig = SinOsc(freq) * amp
    Out(0, sig)

sd = SynthDef('sine', sine)
sd.add()

The sine function defines the relations between ugens. Then the function is passed to the SynthDef object constructor that receives the name of the synthdef as a string and the function. Finally the synthdef is sent to the server by calling its add method.

The sine function contains a SinOsc ugen that receives a scalar argument, freq, and its output signal is then multiplied by another scalar, amp, which creates a * ugen, and the the resulting signal is patched to the output ugen Out. The resulting server instructions can be seen with the following method:

sd.dump_ugens()

Prints:

sine
['0_SinOsc', 'audio', [440, 0.0]]
['1_*', 'audio', ['0_SinOsc', 0.1]]
['2_Out', 'audio', [0, '1_*']]

As the example shows, ugens can be interconnected by either parameters or operations, and also methods that represent operations. Basically, the valid types to combine with ugens are numbers, int or float, lists or tuples containing valid types and other ugens. Some ugens may be able to receive other special objects but they will be internally converted to basic types.

Synthesis definitions can also be written using the decorator function synthdef which creates the SynthDef instance with the name of the function and adds it to the default running server or at the next boot.

@synthdef
def mydef():
    ...

Synthesis nodes

SynthDef objects implements the callable interface to simplify node creation without sacrificing functionality. In sc3 it is possible to create a synthesis definition and node as follows:

@synthdef
def sine(freq=440, amp=0.1, pan=0, gate=1):
    sig = SinOsc(freq) * amp
    env = EnvGen(Env.asr(), gate)
    FreeSelfWhenDone(env)
    Out(0, Pan2(sig * env, pan))

x = sine(220, pan=-0.5)
x.release()

The SynthDef object represents the composed synthesis function and accept positional or keyword arguments as defined by the graph (sine) function. This interface also sends the message in a bundle so it can be used within routines and keep perfect timing.

In addition to the arguments of the function it is also possible to pass the parameters of Synth, target, add_action and register. For example:

g = Group()
x = sine(target=g, add_action='tail')

Multichannel expansion

List perform multichannel expansion as usual:

x = play(lambda: SinOsc([220, 330, 660]).sum() * 0.01)
x.free()

Tuples, as basic Python’s data structures, have a special meaning when used to construct synthdefs, they define lists of values as a single value to prevent multichannel expansion when necessary. For example, to specify vector arguments.

@synthdef
def multi(freq=(220, 330, 550), amp=0.1):
    sig = SinOsc(freq) * [0.25, 0.5, 0.3] * amp
    Out(0, Mix(sig).dup())

x = multi()
x.set('freq', [110, 111, 112])
x.free()

Rates

SynthDef parameters rate is implemented as type annotations. Annotating the parameter with the strings 'ar', 'kr', 'ir' or 'tr' will create the appropriate rate for control ugens. Numbers, as annotation, will create lag controls. It is also possible directly use the class instead of the decorator with all original parameters.

@synthdef
def sine(out=0, freq=440, amp=0.1, trig:'tr'=1):
    sig = SinOsc(freq) * amp
    env = EnvGen(Env.perc(0.02, 2), trig)
    Out(out, sig * env)

@synthdef
def cheaptrem(sig:'ar'=0, freq:'ir'=4, amp:'kr'=1):
    sig = In(sig)
    ctl_pan = SinOsc.kr(freq)
    ctl_amp = ctl_pan.range(0, 1) * amp
    Out(0, Pan2(sig * ctl_amp, ctl_pan))

g = Group()
b = AudioBus()

fx = cheaptrem(b, target=g)
x = sine(b, target=g, add_action='head')

x.set('trig', 1)
x.set('trig', 1)

x.free()
fx.free()