[ ]:
import time

import sc3nb as scn
from sc3nb import SynthDef, Synth
[ ]:
sc = scn.startup()

SynthDef

SynthDef wraps and extends the flexibility of SuperCollider Synth Definitions

To see how to create and use a Synth from the SynthDef please also see the Synth examples

SynthDef creation

[ ]:
synth_def = SynthDef('random',
"""{ |out|
    var osc, env, freq;
    freq = Rand(400, 800);
    osc = SinOsc.ar(freq, 0, 0.2);
    env = Line.kr(1, 0, 1, doneAction: Done.freeSelf);
    Out.ar(out, osc * env);
}""")

Note that you can copy the representation to you SuperCollider IDE

[ ]:
synth_def

SynthDefs are created via sclang when you add them.

[ ]:
synth_def.add()
[ ]:
Synth("random")

Loading SynthDefs

You can also load SynthDefs from .scsynthdef files or as bytes via the Server

The Server allows - to send binary SynthDef content with send_synthdef

[ ]:
sc.server.send_synthdef?
  • load a SynthDef file with load_synthdef

  • load a directory with SynthDef files with load_directory

[ ]:
sc.server.load_synthdef?

This can be used together with supriya for example.

Note: These synth are not known to sclang, therefore getting the SynthDesc won’t work

[ ]:
try:
    from supriya.synthdefs import SynthDefBuilder, Envelope
    from supriya.ugens import SinOsc, EnvGen, Out
    from supriya import DoneAction
except:
    print("Example needs supriya pakage installed")
else:
    with SynthDefBuilder(name='supriya_synth', amplitude=0.3, frequency=440.0, gate=1.0) as builder:
        source = SinOsc.ar(frequency=builder['frequency'])
        envelope = EnvGen.kr(done_action=DoneAction.FREE_SYNTH,
                             envelope=Envelope.asr(),
                             gate=builder['gate'])
        source = source * builder['amplitude']
        source = source * envelope
        out = Out.ar(bus=0, source=source)

    # Build SynthDef with supriya SynthDefBuilder
    supriya_synthdef = builder.build()
    # Compile SynthDef to binary SynthDef and send it to the server.
    sc.server.send_synthdef(supriya_synthdef.compile())

    with sc.server.bundler(0.1) as bundler:
        syn1 = scn.Synth(supriya_synthdef.actual_name)  # the SynthDef name is saved in the supriya_synthdef
        bundler.wait(0.5)
        syn1.release(0.5)

        bundler.wait(1)

        syn2 = scn.Synth(supriya_synthdef.actual_name, controls={"frequency": 220})
        bundler.wait(1)
        syn2.free()

SynthDef creation with context

the sc3nb SynthDef - different from standard sclang SynthDefs - allows to inject a number of context specifiers using mustache syntax, i.e. {{my_context_specifier}}. This would mark a place within the synth definition that can be replaced by some user-wished sclang code before adding the SynthDef to the server.

You can create a ‘proto’ synth definition with the SynthDef

[ ]:
synth_def_context = SynthDef(name="myKlank", definition=r"""
{ |out=0, amp=0.3, freq=440|
    var klank = DynKlank.ar(`[[1,2], [1,1], [1.4,1]], {{EXCITER}}, freq);
    Out.ar(out, amp*klank!^p_channels);
}""")

In this example a new definition named "myKlank" offers a dynamic context {{EXCITER}}” and 3 value contexts freqs, rings, and channels. Now we want to replace the dynamic context EXCITER with user-specific code.

  • use set_context() to replace a context with specific code

[ ]:
synth_def_context.set_context("EXCITER", "Dust.ar(20)")
  • the specific code can include other context specifier strings in turn

    • this basically allows to create chained processing in python-compiled synths defs.

Remarks: * The context mechanism is very general: * it can be used both for code and values * e.g. for an array specification or a UGen selection. * To set a value (e.g. number or array), consider to use the pyvars syntax, i.e. using the caret ‘^’ variable value injection of python variables into sc code

The definition is complete up to the pyvars. So let’s create a myKlank in SC!

[ ]:
p_channels = 2
[ ]:
synth_def_context  # will show ^p_channels value when defined
[ ]:
synth_name = synth_def_context.add(name="kdust")
[ ]:
# for testing, let's create a synth and stop after 1s
%scv x = Synth.new(^synth_name, [\freq, 200, \amp, 0.05])
time.sleep(1)
%scv x.free

Now let’s create another synthdef with a WhiteNoise excitation, but as a 1-channel version

[ ]:
p_channels = 1
[ ]:
synth_def_context.reset()
knoise = synth_def_context.set_context("EXCITER", "WhiteNoise.ar(0.2)").add(name="knoise")
knoise
[ ]:
# for testing, let's create a synth and stop after 1s
%scv x = Synth.new(^knoise, [\amp, 0.05, \freq, 100])
time.sleep(1)
%scv x.free

To delete an existing synthdef in sc you can use

synthDef.free(name)

(but don’t do it now as the synthdef is used for examples below)

Remove all unused placeholders from current_def by

[ ]:
synth_def_context.reset()
print(f"SC code with context vars: \n {synth_def_context.current_def} \n")
synth_def_context.unset_remaining()
print(f"SC code with unset context vars: \n {synth_def_context.current_def} \n", )

Here you see, that the placeholder {{EXCITER}} has been deleted. * With this method you make sure, that you don’t have any unused placeholders in the definition before creating a SynthDef. * Note, however, that your code might then not be functional…

Example creation of many SynthDefs

In some cases you want to create many SynthDefs with only a small change. You can use the SynthDefs object multiple time to do this. Here we want to create playbuf synthdefs for 1 to 10 channels: (Reuse of the synthdef object, which is defined above)

[ ]:
playBufsynthDef = SynthDef("playbuf_", """
    { |out=0, bufnum=1, rate=1, loop=0, pan=0, amp=0.3 |
            var sig = PlayBuf.ar(^num_channels, bufnum,
                rate*BufRateScale.kr(bufnum),
                loop: loop,
                doneAction: Done.freeSelf);
            Out.ar(out, Pan2.ar(sig, pan, amp))
    }"""
)
[ ]:
synthPlaybufs = {}
for num in [1,2,4,8]:
    synthPlaybufs[num] = playBufsynthDef.add(pyvars={"num_channels": num}, name=f"playbuf_{num}")

Now you can access via synthPlayBufs[2] to the 2-ch playbuf etc.

[ ]:
synthPlaybufs[2]

Use-case: DynKlank Synths with controllable nr. of filters

A problem with synthdefs is that some parameters can only be set at compile time. E.g. * A DynKlank needs to know the maximum nr. of filters in its filter bank at SynthDef time. * A synth will need to know the channel count at SynthDef time

Contexts allow to define such synthDefs dynamically on demand.

The following code is a dynamic DynKlank whose data-controlled nr. of filters is determined via the SynthDef class. * nr of channels and nr. of filters in the filter bank are specified via pyvars * TODO: find a way how to set amps, rings, and harms on Synth.new

[ ]:
scn.SynthDef
[ ]:
synth_def = scn.SynthDef(name="myKlank", definition=r"""
{ |out=0, amp=0.3, freq=440|
    var klank, n, harms, amps, rings;
    harms = \harms.kr(Array.series(^p_nf, 1, 1));
    amps = \amps.kr(Array.fill(^p_nf, 0));
    rings = \rings.kr(Array.fill(^p_nf, 0.1));
    klank = DynKlank.ar(`[harms, amps, rings], {{EXCITER}}, freq);
    Out.ar(out, amp*klank!^p_channels);
}""")
[ ]:
# now create a synth where exciter is Dust, with 10 filters and stereo
kdust = synth_def.set_context("EXCITER", "Dust.ar(80)").add(
    pyvars={"p_nf": 10, "p_channels": 2})
print(kdust)
[ ]:
x = scn.Synth(name=kdust,
              controls={"freq": 100,
                        "amp": 0.05,
                        "harms": [60,60,60],
                        "amps": [0.1,0.1,0.1],
                        "rings": [1, 0.4, 0.2],})

[ ]:
x.set({"harms":[1,2,6], "amps": [0.1,0.1,0.1], "rings": [1, 0.4, 0.2]})
[ ]:
# following syntax works the same:
# x.set(["harms",[1,2,6],"amps",[0.1,0.1,0.1],"rings",[1, 0.4, 0.2]])
[ ]:
x.free()

Getting a SynthDesc

You can also query for a SynthDesc via sclang with the class method get_desc("synth_name")

Lets see for the SynthDefs included in sc3nb

  • SynthDef “s1” which is a discrete sound event with parameters

    • frequency freq

    • duration dur

    • attack time att

    • amplitude amp

    • number of harmonics num

    • spatial panning pan

[ ]:
scn.SynthDef.get_description("s1")
  • SynthDef “s2” which is a continuous synth with parameters

    • frequency freq

    • amplitude amp

    • number of harmonics num

    • spatial panning pan

    • Exponential lag lg

    • EnvGen gate gate, which allows the Node.release method

[ ]:
scn.SynthDef.get_description("s2")
[ ]:
sc.exit()
[ ]: