#Chapter 1 Debugging techniques In a perfect world every line of code you type in immediately works as expected. If you are like most people, however, you will make tons of mistakes. SuperCollider does not have a built-in debugger. So how do we go about debugging problems? We'll go over some ways to debug your SuperCollider programs.
Printing techniques
Printing content of variables
SuperCollider offers a few method calls to post information to the post window. The most common
used ones are postln
, postcs
and debug
.
postln
prints the value of the variable in the post window and automatically adds a newline.post
does the same but without adding a newlinepostcs
prints the value in the form of a compile string in the post window and also adds a newlinedebug
takes an argument and uses it as prefix when printing the value
Some examples to make it clear
(
var num = 42;
var txt = "important message";
txt.postln; // important message
num.postln; // 42
txt.postcs; // "important message" (note the additional quotes)
num.postcs; // 42 (note no additional quotes)
txt.debug("text"); // text: important message
)
From the examples you can already understand where postcs
can be useful: one common type of bug is
using a string where you expect a number (or vice versa). by using postcs
the difference is
immediately visible.
Printing type of a variable
Sometimes you want to see more detailed information about the exact type of a variable. In that
case you can postln
its class.
(
var pat = Pbind(\instrument, \default);
var player = pat.play;
pat.class.postln; // Pbind
fork {
player.class.postln; // EventStreamPlayer
3.wait;
player.stop;
}
)
Printing intermediate results in complex expressions
Supercollider forces you to declare your variables at the top of the function definition. Sometimes this can be annoying if you want to quickly inspect the values of some variables before some later calculations, because it forces you to rewrite your code.
A cool feature of print methods like postln
, postcs
and debug
is the fact that they return the
object they work on after printing it to the post window.
Suppose we want to debug the following code
(
var one = 1;
var two = "2";
var division = one/two;
var double = division*2;
division.debug("division");
)
The division.debug will never be executed, because the initialization of division in the line before
already crashes. If you want to examine the values of one
and two
before the division happens,
you could refactor your code to separate the declaration of division from its initialization.
The more complex the code becomes, the more work this takes.
(
var one = 1;
var two = "2";
var division;
var double;
one.postcs;
two.postcs;
division = one/two;
division.debug("division");
double = division*2;
)
Because the post and debug family of calls return the object they operate on, you can often avoid rewriting your code just for debugging purposes.
(
var one = 1;
var two = "2";
var division = one.postcs/two.postcs;
var double = division*2;
division.debug("division");
)
Spoiler: The problem in the code is that you're trying to divide a number by a string.
Concatenating messages
A final family of techniques that can be useful to know in the context of debugging are some different approaches to concatenating strings to form a longer, more informative line of text.
- The operator
++
concatenates representations of objects without space. To get a string as result of the concatenation, the left hand side of a ++ expression must be a string. - The operator
+
concatenates representations of objects and automatically adds a space in between. Either left hand side or right hand side has to be a string.
(
var t1 = "txt";
var t2 = "another one";
var n1 = 2.0;
var n2 = 3.0;
(t1 ++ t2).postcs; // "txtanother one"
(t1 + n1).postcs; // "txt 2.0"
//(n1 ++ t1).postcs; // ERROR: Message '++' not understood
(n1 + t1).postcs; // "2.0 txt"
(n1 + n2).postcs; // 5.0
("" ++ n1 + n2).postcs; // "2.0 3.0"
)
Notice the use of brackets. If you forget them, as in t1 ++ t2.postcs
you only print the value of
t2.
Another approach to make longer strings is by using a call to format
. The following is an example:
(
var n1=2;
var n2=3;
"if you add % to % you get %".format(n1, n2, n1+n2).postln;
)
In these strings, the percentage character (%) is used as a placeholder, to be replaced with a value. If you need to print a percentage character, use \\% instead.
Debugging patterns
Printing values produced by patterns
A neat trick to monitor which values your patterns are producing for certain keys is calling
trace
. trace
prints out these values to the post window. Example: why don't the pat1 rhythms
sound as random as those produced by pat2?
Compare the output of the duration pattern in the following two code fragments:
(
s.waitForBoot({
var pat1 = Pbind(
\instrument, \default,
\dur, Pwhite(0,1,inf).trace,
\midinote, Pbrown(40,70,1,inf)
);
~player = pat1.play;
});
)
// output:
1
0
1
0
1
1
...
versus
(
s.waitForBoot({
var pat2 = Pbind(
\instrument, \default,
\dur, Pwhite(0.0,1.0,inf).trace,
\midinote, Pbrown(40,70,1,inf)
);
~player = pat2.play;
});
)
// output:
0.01384711265564
0.56002390384674
0.79622256755829
...
trace
can be used on any pattern, so you could just as well write the following to observe all
values produced by the pattern
(
s.waitForBoot({
var pat1 = Pbind(
\instrument, \default,
\dur, Pwhite(0,1,inf),
\midinote, Pbrown(40,70,1,inf)
);
~player = pat1.trace.play;
});
)
// output:
( 'instrument': default, 'dur': 1, 'midinote': 43 )
( 'instrument': default, 'dur': 0, 'midinote': 44 )
...
Customized debug messages
If you need more flexibility in how to represent the values, you can add an extra key in the pattern. One property of patterns is that every key you add to them is evaluated when it's time to produce a new event. By using Pfunc, you get access to all the keys specified before the extra key in the pattern.
In the following example, we add the key \myfunnykey. Note that this key has no predefined meaning, but like any other key it is evaluated when it's time to produce a new event. The Pfunc pattern takes an event as argument. Via this event argument you can access the values of the keys \instrument and \dur which are specified before \myfunnykey in the pattern.
In the example, the value of \midinote is not yet known when \myfunnykey is evaluated because it is specified after \myfunnykey, and therefore we cannot print it. If you move \myfunnykey to the very bottom of the Pbind specification, you'd be able to print the value of \midinote as well.
(
s.waitForBoot({
var pat1 = Pbind(
\instrument, \default,
\dur, Pwhite(0.0,1.0,inf),
\myfunnykey, Pfunc({
| event |
"The pattern produces a duration of % with instrument %".format(event[\dur], event[\instrument]).postln;
"midi note is set to % (this should be wrong because midinote is not known yet)".format(event[\midinote]).postln;
}),
\midinote, Pbrown(40,70,1,inf)
);
~player = pat1.play;
});
)
//output:
The pattern produces a duration of 0.782435297966 with instrument default
midi note is set to a Function (this should be wrong because midinote is not known yet)
The pattern produces a duration of 0.40264010429382 with instrument default
midi note is set to a Function (this should be wrong because midinote is not known yet)
...
Try to move \midinote before \myfunnykey in the previous example and observe the difference in output.
Tip: The same mechanism that is used here to produce custom debugging messages can also be used to build real-time visualizations of patterns while they are playing. Instead of printing messages to the post window, one then updates elements in a user interface. One example of such a system can be found on sccode.org
Debugging UGens
Printing values produced by UGens
A low-tech, but useful operation is to print out values produced by some UGen. As UGens typically
run on the server, and printing is something that happens in the language, you might expect we'll
need some mechanism to transfer data from the server to the language. You would be right, but
SuperCollider UGens implement a convencience method poll
that hides all of this complexity.
Example:
(
s.waitForBoot({
// specify what a wobble looks like
SynthDef(\wobble, {
var out=\out.kr(0), freq=\freq.kr(440), amp=\amp.kr(0.1);
var env = EnvGen.ar(Env.perc(0.01, 2.0), doneAction:Done.freeSelf);
var sig = amp*env*SinOsc.ar(3).range(0.5,1)*SinOsc.ar(freq);
Out.ar(out, sig!2);
}).add;
// make sure the synth has arrived on the server before continuing
s.sync;
// play a wobble
Synth(\wobble);
});
)
Imagine you want to examine in more detail why wobble wobbles. The call to range(0.5,1)
catches your
attention, and you are curious what values it produces. By using poll
, its value will be printed to
the post window at regular intervals:
(
s.waitForBoot({
// specify what a wobble looks like
SynthDef(\wobble, {
var out = \out.kr(0), freq = \freq.kr(440), amp = \amp.kr(0.1);
var env = EnvGen.ar(Env.perc(0.01, 2.0), doneAction:Done.freeSelf);
var sig = amp*env*SinOsc.ar(3).range(0.5,1).poll*SinOsc.ar(freq);
Out.ar(out, sig!2);
}).add;
// make sure the synth has arrived on the server before continuing
s.sync;
// play a wobble
Synth(\wobble);
});
)
// output:
UGen(MulAdd): 0.750098
UGen(MulAdd): 0.987736
UGen(MulAdd): 0.602984
UGen(MulAdd): 0.603118
Note that as written above, you only see the output of SinOsc.ar(3).range(0.5,1)
but not of other
stuff surrounding it. If you want to see the output of - say - amp*env*SinOsc.ar(3)*range(0.5,1)
instead, you could slightly restructure your code as in the following example.
(
s.waitForBoot({
SynthDef(\wobble, {
var out = \out.kr(0), freq = \freq.kr(440), amp = \amp.kr(0.1);
var env = EnvGen.ar(Env.perc(0.01, 2.0), doneAction:Done.freeSelf);
var partialsignal = amp*env*SinOsc.ar(3).range(0.5,1);
var sig = partialsignal.poll*SinOsc.ar(freq);
Out.ar(out, sig!2);
}).add;
s.sync;
Synth(\wobble);
});
)
// output:
UGen(BinaryOpUGen): 0.000635417
UGen(BinaryOpUGen): 0.082192
UGen(BinaryOpUGen): 0.0408766
UGen(BinaryOpUGen): 0.0332704
Calling poll
will print new values with a predefined frequency. If you want more flexibility you
can pass in some arguments. In the following example, the values are printed 30 times per second
(everytime the first argument produces a pulse) with a prefix "my funky msg".
(
s.waitForBoot({
SynthDef(\wobble, {
var out = \out.kr(0), freq = \freq.kr(440), amp = \amp.kr(0.1);
var env = EnvGen.ar(Env.perc(0.01, 2.0), doneAction:Done.freeSelf);
var partialsignal = SinOsc.ar(3).range(0.5,1);
var sig = amp*env*partialsignal.poll(Impulse.ar(30), "my funky msg")*SinOsc.ar(freq);
Out.ar(out, sig!2);
}).add;
s.sync;
Synth(\wobble);
});
)
// output:
my funky msg: 0.750098
my funky msg: 0.897103
my funky msg: 0.987823
my funky msg: 0.987705
Tip: Other ways exist to send data from the server to the language, which allow greater flexibility in what to do with it (like building custom graphical visualizations) but they would lead us a bit too far in the context of debugging. If you can't wait to learn about these other ways to communicate data from server to language, you can research some examples from a course given by Stelios Manousakis. One of these alternative mechanisms sends data over a bus. This also happens to be the mechanism underlying the next topic.
Visualizing UGen output with a FreqScope
FreqScope is another way to observe output created by a UGen. It monitors data posted on a Bus and visualizes it on an oscilloscope. Because it looks at a bus, it remains completely decoupled from the UGen that produces the data.
In the following example, the \sine UGen posts data on output bus 0 and FreqScope observes output bus 0.
(
s.waitForBoot({
SynthDef(\sine, {
var out = \out.kr(0), freq = \freq.kr(440), amp = \amp.kr(0.1);
var env = EnvGen.ar(Env.perc(0.01, 5.0), doneAction:Done.freeSelf);
var partialsignal = SinOsc.ar(3).range(0.5,1);
var sig = amp*env*partialsignal.poll(Impulse.ar(30), "my funky msg")*SinOsc.ar(freq);
Out.ar(out, sig!2);
}).add;
s.sync;
// create a new analyzer
FreqScope.new(400, 200, 0, server: s);
// play sine
Synth(\sine).play;
});
)