supercollider

In supercollider, what's the role of "*", "#", "_"?


I've been reading through a lot of tutorials and forums on SuperCollider lately, and came accross on some code samples using * and # and _, but I don't find why they are used ? Can someone explain that to me ? Is this related to avoiding multichannel expansion ?

Some samples:

(
var list = [1, 2, 3];
func(*list);  // equivalent to func(list[0], list[1], list[2])
)

or

var a, b, c;
#a, b, c = [1, 2, 3]; // equivalent to a=1; b=2; c=3;

or

p = Pbind(
    \degree, Pwhite(-7, 12, inf),
    \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
    \legato, Pfunc { ~legato }    // retrieves value set by MIDI control
).play;
)

or (in the Pseq)

(
Pbind(
    \mtranspose, -1,
    \octave, 5,
    [\dur, \degree], Ptuple(y.collect(Pseq(_, inf)))
).play
)

Solution

  • Asterix

    Your sample quickly shows a summary of how this works, but let's expand it a bit and define a three argument function:

    f = { arg a, b, c;
    a + b + c;
    };
    

    When we want to call the function, we could do: f.value(1,1,1), which returns 3.

    Now say for some reason we have an array of three numbers and we want to pass them to the function.

    a = [1, 1, 1];
    

    If we just try f.value(a) we just an error. We could work around this by specifying f.value(a[0], a[1], a[2]) or we can use the * shortcut. This breaks up the array into a list for passing to a function. So the equivalent, shorter syntax is f.value(*a)

    Pound

    The pound/hash sign (whichever you call it) can to two different things, depending where it goes. Let's start with an array:

    a = [1, 2, 3];
    

    Let's say you have three variables and you want them to get the array contents. You could do:

    b = a[0];
    c = a[1];
    d = a[2];
    

    But that's a lot of typing, so an equivalent shortcut is

    #b, c, d = a
    

    The other meaning of # is when it comes right before the opening bracket of an array. According to Nick Collins:

    A note about efficiency

    You will occasionally see

     #[1,2,3] //makes a totally fixed (non-dynamic) Array and is slightly cheaper, especially where you're not going to change the Array once you make it
    

    rather than

     [1,2,3]    //a dynamic array 
    

    To show the difference

     a= #[1,2,3];
     
     
     a[0] //works
     
     
     a[0] = 8 //fails, because it can't be changed
    

    (from https://composerprogrammer.com/teaching/supercollider/sctutorial/Technicalities/02%20Arrays.html )

    Underscore

    The underscore is doing something called Partial Application. It has a helpfile: https://doc.sccode.org/Reference/Partial-Application.html

    What it's doing is replacing an argument declaration and usage. So in one of their examples, you could write:

    (1..10).collect({ arg n; n.squared });
    

    Or you could shorten it slightly:

    (1..10).collect( _.squared );
    

    So the _ is taking over declaring and using n. Note that the _ also means that you don't use the {} brackets. In your example code, Ptuple(y.collect(Pseq(_, inf))) could be rewritten as Ptuple(y.collect({arg z; Pseq(z, inf)}))

    Syntax Shortcuts

    All of these shortcuts collectively are called "Syntactic Sugar". There's a helpfile called that, but if you're just learning SuperCollider, I suggest you avoid it for now and only dip in hen you come across a symbol doing something strange.