Adding Synthesizers
When you install SuperDirt, you also install a small library of default synthesizers. These synths are made specifically for Tidal and SuperDirt. They do sound nice, but at some point you will want to create your own synthesizers. This page will guide you and teach you the basic steps of synthesizer creation for SuperDirt.
Building a simple synthesizer
Learning SuperCollider
If you want to build new synthesizers for Tidal Cycles, you will need to learn some rudiments of the SuperCollider language (SCLang) as well. There are many guides, courses and tutorials you can find on the internet. I personnally recommand the Eli Fieldsteel YouTube channel. It is the most complete and beginner friendly tutorial you can find. It starts from the very basics up to very advanced topics for the more courageous.
When you play with Tidal Cycles, SuperCollider and SuperDirt are in charge of handling audio. Everything audio-related on Tidal will happen on the SuperCollider side. Even if you haven't planned to learn more about audio synthesis, it is important to keep this architectural distinction in mind.
SuperDirt template
Everything starts with this boilerplate SynthDef
that you need to copy and paste in your SuperCollider interactive editor:
SynthDef(\test, {
| out, sustain=1, freq=440, speed=1, begin=0, end=1, pan, accelerate, offset|
}).add;
In SuperCollider, a SynthDef
is a definition of something that will be instantiated as a Synth
node. Don't be affraid of the technical jargon, it just means that we are going to declare a function that will be the definition of our synthesizers.
Everything inside the ||
is a list of arguments: a list of required parameters for our synthesizer to work. You might recognize some Tidal oddities, such as the accelerate
parameter, or the begin
and end
parameters.
We give our synthesizer a \name
, here (\test
). This way, SuperCollider
will be able to retrieve it on-the-fly from its internal list of synths. The .add
method simply means "add it to the server".
A SynthDef
can be overriden at any moment. You can play a pattern on the Tidal side and design/recompile your synth on-the-fly. This is a great way to test if your synth works well.
Blip-blop state
So far so good. We have an empty shell kind of synth. Strictly speaking, our synth is an audio function, and it lives between the curly brackets {}
. Some of you might like silence, but we want sound.
What are UGens?
We will compose our synth by chaining together UGens
. What is a UGen
? The SuperCollider help file tells us:
UGens represent calculations with signals. They are the basic building blocks of synth definitions on the server, and are used to generate or process both audio and control signals
Think about them as "audio bricks". They are tiny components, each one representing a function or a modification of an incoming signal. Some UGens
are creating signals from scratch (oscillators, enveloppes), some are modifying the signals (filters, resonators), some are distributing it (stereo, ambisonics). UGens
are ubiquitous in computer music, and you might have encountered them already in another language/another software/another form: Max ~objects
, modules in modular synthesis, etc... Since the dawn of computer music, there is a convention around the fact that UGens
have different "rates" depending on their usage:
audio rate
:.ar
in SuperCollider. An audio rateUGen
will run at your current audio sample rate, generally44.1hz
per second..ar
signals are used for audible components (oscillators).control rate
:.kr
in SuperCollider. Control rates are used for signals when the sampling rate is not crucial (enveloppes and LFOs). They are generally running atsamplerate/some amount
.initial rate
:.ir
in SuperCollider. A static non-modulable rate. It is more efficient on the CPU compared to a regular argument. But yeah, sounds like some sort of variable.
Basic synthesizer
In the following example, I've arranged everything you need to get a basic synthesizer running:
SynthDef(\test, {
| out, sustain=1, freq=440, speed=1, begin=0, end=1, pan, accelerate, offset|
var env = Line.ar(1, 0, sustain);
var osc = SinOsc.ar(freq);
var output = osc * env;
OffsetOut.ar(out, Pan2.ar(in: output, pos: pan));
}).add;
These four lines alone are enough to make a basic synthesizer. Notice that we are introducing new variables using the var blabla = ...
syntax. We added the following components:
osc
:SinOsc
is a basic sinusoïdal oscillator, running atfreq
speed.env
:Line
is a line generator, running from1.0
to0.0
oversustain
seconds.output
: by multiplyingosc
byenv
, we created an amplitude enveloppe for our synth, turning a continuous signal into discrete notes.OffsetOut
: the audio output itself. The first argument is the buffer we want to write the sound to. If we leaveout
unspecified as it is the case here, SuperCollider will direct the sound to the audio output. How convenient! The following arguments are used to pass the signaloutput
to the buffer.Pan2
: thisUGen
will turn our mono signal into a stereo signal.
You can test it by running the following pattern with Tidal:
d1
$ cat[
note "c ds g bf", note "c ds g bf",
note "c f af c6", note "c f af c6"]
# s "test"
Free the synthesizer
Our synth is currently working but something is wrong. The synth
will never die, meaning that each note we will play will slowly increase the memory usage until audio glitches and other problems start to appear, apparently at random. SuperCollider can fix that by using DoneAction
. Take a look at this updated version:
SynthDef(\test, {
| out, sustain=1, freq=440, speed=1, begin=0, end=1, pan, accelerate, offset|
var env = Line.ar(1, 0, sustain, doneAction: Done.freeSelf);
var osc = SinOsc.ar(freq);
var output = osc * env;
OffsetOut.ar(out, Pan2.ar(in: output, pos: pan));
}).add;
Using doneAction
is extremely important. Our synth will now free whatever resource it was using while playing.
Adapt your synth to SuperDirt
SuperDirt is Tidal's audio engine. If you wish to use your synth with SuperDirt, there are a couple more things you should take care of. Remember the OffsetOut
part? We will improve it in order to make it compatible with Tidal. We were hearing sound, but the sound was not managed and handled by SuperDirt itself but by the vanilla SuperCollider audio server instance.
Take a look at this new version of our Blip-blop
synthesizer:
SynthDef(\test, {
| out, sustain=1, freq=440, speed=1, begin=0, end=1, pan, accelerate, offset, volume|
var env = Line.ar(1, 0, sustain, doneAction: Done.freeSelf);
var osc = SinOsc.ar(freq);
OffsetOut.ar(out,DirtPan.ar(osc, ~dirt.numChannels, pan, env));
}).add;
As you can see, we are now using special objects for the audio output: DirtPan
, as well as a reference to ~dirt.numChannels
. It a SuperDirt compatible version of what we were doing so far: outputting in stereo with a pan
parameter. The only difference is that... now it works. Test this synth with the following Tidal pattern:
d1
$ superimpose (fast 2 . (|+ note "12 0 24 -12 0"))
$ cat[
note "c ds g bf", note "c ds g bf",
note "c f af c6", note "c f af c6"]
# s "test" # pan (slow 2 $ range "-1" 1 $ sine)
It sounds really nice! Your synth is now totally compatible with SuperDirt.
Note that we changed a few things:
output
: it's gone. We didn't needed it. We are now feeding the enveloppe toDirtPan
osc
is directly fed toDirtPan
as well.
More complex synthesis
This page taught you to create a synthesizer for SuperDirt but it is still pretty basic. If you learn a bit more about SuperCollider, you will be able to refine your ideas. Take a look at the following SynthDef
. Keep the same pattern running, it sounds nice:
SynthDef(\elegiac, {
| out, sustain=1, freq=440, speed=1, begin=0, end=1, pan, accelerate, offset, volume|
var env = Line.ar(1, 0, sustain, doneAction: Done.freeSelf);
var osc = RLPF.ar(in: SawDPW.ar([freq, freq/2]), freq: SinOsc.ar(pan).range(200,2000));
OffsetOut.ar(out,DirtPan.ar(osc, ~dirt.numChannels, pan, env))
}).add;
Using custom parameters
If you want to create any custom parameter for your SynthDef
, it also has to be referenced in Tidal.
To do this, you have to create a parameter in Tidal with the same name the argument has in SuperCollider.
For example, if the arguments in SuperCollider were | harm, pit, model |
, you should add this to your Tidal Boot File:
let harm = pF "harm"
let pit = pF "pit"
let model = pI "model"
Listing Tidal paramenters
From SuperCollider, you can generate a Tidal parameter list for any SuperDirt SynthDef:
SuperDirt.postTidalParameters([\imp, \default]);
SuperDirt.postTidalParameters([\supersaw, \default])
This will generate the exact parameters registered with Tidal for the "imp" or "supersaw" synths.
-- | parameters for the SynthDefs: imp, default
let (begin, begin_p) = pF "begin" (Nothing)
(end, end_p) = pF "end" (Nothing)
(freq, freq_p) = pF "freq" (Nothing)
(span, span_p) = pF "span" (Nothing)
(speed, speed_p) = pF "speed" (Nothing)
Troubleshooting
I can hear 'clicks'
When using your custom synthesizers for Tidal, you will sometimes hear 'clicks'. These clicks are breaks/discontinuities in the audio signal. Audio clicks are ubiquitous in computer music, and people are doing everything they can to avoid them and to fix the problem.
If you can hear audio clicks while playing with your custom SuperCollider synths, try the following:
- rewrite your
SynthDef
the Tidal way (see above). - raise the
legato
value in your pattern. - increase the
fadeTime
parameter in SuperDirt:
~dirt.orbits[3].set(\fadeTime, 0.01);
~dirt.orbits[4].set(\fadeTime, 0.1);