This page was generated from examples/supercollider-objects/synthdef-examples.ipynb.
[1]:
import time
import sc3nb as scn
from sc3nb import SynthDef, Synth
[2]:
sc = scn.startup()
Starting sclang process... [sclang | start reading ]
Done.
Registering OSC /return callback in sclang... Done.
Loading default sc3nb SynthDefs... Done.
Booting SuperCollider Server... [scsynth | start reading ]
Done.
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
[3]:
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
[4]:
synth_def
[4]:
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);
})
SynthDefs are created via sclang when you add
them.
[5]:
synth_def.add()
[5]:
'random'
[6]:
Synth("random")
[6]:
<Synth(20001) 'random' s {}>
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
[7]:
sc.server.send_synthdef?
load a SynthDef file with
load_synthdef
load a directory with SynthDef files with
load_directory
[8]:
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
[9]:
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()
Example needs supriya pakage installed
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
[10]:
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
[11]:
synth_def_context.set_context("EXCITER", "Dust.ar(20)")
[11]:
SynthDef('myKlank',
{ |out=0, amp=0.3, freq=440|
var klank = DynKlank.ar(`[[1,2], [1,1], [1.4,1]], Dust.ar(20), freq);
Out.ar(out, amp*klank!^p_channels);
})
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!
[12]:
p_channels = 2
[13]:
synth_def_context # will show ^p_channels value when defined
[13]:
SynthDef('myKlank',
{ |out=0, amp=0.3, freq=440|
var klank = DynKlank.ar(`[[1,2], [1,1], [1.4,1]], Dust.ar(20), freq);
Out.ar(out, amp*klank!2);
})
[14]:
synth_name = synth_def_context.add(name="kdust")
[15]:
# 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
-> Synth('kdust' : 1000)
-> Synth('kdust' : 1000)
Now let’s create another synthdef with a WhiteNoise excitation, but as a 1-channel version
[16]:
p_channels = 1
[17]:
synth_def_context.reset()
knoise = synth_def_context.set_context("EXCITER", "WhiteNoise.ar(0.2)").add(name="knoise")
knoise
[17]:
'knoise'
[18]:
# 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
-> Synth('knoise' : 1000)
-> Synth('knoise' : 1000)
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
[19]:
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", )
SC code with context vars:
{ |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);
}
SC code with unset context vars:
{ |out=0, amp=0.3, freq=440|
var klank = DynKlank.ar(`[[1,2], [1,1], [1.4,1]], , freq);
Out.ar(out, amp*klank!^p_channels);
}
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)
[20]:
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))
}"""
)
[21]:
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.
[22]:
synthPlaybufs[2]
[22]:
'playbuf_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
[23]:
scn.SynthDef
[23]:
sc3nb.sc_objects.synthdef.SynthDef
[24]:
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);
}""")
[25]:
# 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)
myKlank
[26]:
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],})
[27]:
x.set({"harms":[1,2,6], "amps": [0.1,0.1,0.1], "rings": [1, 0.4, 0.2]})
[27]:
<Synth(20002) 'myKlank' s {'freq': 100, 'amp': 0.05, 'harms': [1, 2, 6], 'amps': [0.1, 0.1, 0.1], 'rings': [1, 0.4, 0.2]}>
[28]:
# following syntax works the same:
# x.set(["harms",[1,2,6],"amps",[0.1,0.1,0.1],"rings",[1, 0.4, 0.2]])
[29]:
x.free()
[29]:
<Synth(20002) 'myKlank' f {'freq': 100, 'amp': 0.05, 'harms': [1, 2, 6], 'amps': [0.1, 0.1, 0.1], 'rings': [1, 0.4, 0.2]}>
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
[30]:
scn.SynthDef.get_description("s1")
[30]:
{'freq': SynthArgument(name='freq', rate='control', default=400.0),
'amp': SynthArgument(name='amp', rate='control', default=0.30000001192092896),
'num': SynthArgument(name='num', rate='control', default=4.0),
'pan': SynthArgument(name='pan', rate='control', default=0.0),
'dur': SynthArgument(name='dur', rate='control', default=0.4000000059604645),
'att': SynthArgument(name='att', rate='control', default=0.009999999776482582),
'curve': SynthArgument(name='curve', rate='control', default=-2.0)}
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 theNode.release
method
[31]:
scn.SynthDef.get_description("s2")
[31]:
{'freq': SynthArgument(name='freq', rate='control', default=400.0),
'amp': SynthArgument(name='amp', rate='control', default=0.30000001192092896),
'num': SynthArgument(name='num', rate='control', default=4.0),
'pan': SynthArgument(name='pan', rate='control', default=0.0),
'lg': SynthArgument(name='lg', rate='control', default=0.10000000149011612),
'gate': SynthArgument(name='gate', rate='control', default=1.0)}
[32]:
sc.exit()
Quitting SCServer... [scsynth | reached EOF ]
Done.
Exiting sclang... [sclang | reached EOF ]
Done.
[ ]: