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... Done.
Registering OSC /return callback in sclang... Done.
Loading default sc3nb SynthDefs... Done.
Booting SuperCollider Server... 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),
'dur': SynthArgument(name='dur', rate='control', default=0.4000000059604645),
'att': SynthArgument(name='att', rate='control', default=0.009999999776482582),
'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)}
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... Done.
Exiting sclang... Done.
[ ]: