Sunday, April 09, 2006

Fast Pbind-based Introduction for Experienced Programmers

You may wish to quickly scan the first post to find a bit of background

Online resources

The program

starting SuperCollider
  • Once the program is downloaded and running, it should look like the image at left.
  • Boot a server by clicking on the boot button of the localhost server or the internal server
  • Put your code in a new window
  • run code by selecting it and hitting enter (not return)
  • stop code by hitting command-.
  • get help by selecting the name of a class (or help topic) and typing command-?
  • if you enclose code in parenthesis, you can click on the parenthesis to select a block
  • SuperCollider is a dialect of SmallTalk
  • the server is a different process than the language. they communicate via a protocol called OSC

Code examples

First Program

 Pbind.new.play
  • Pbind is a class. All class names start with capital letters and nothing else can.
  • new is a message, which, by convention, causes a new instance of the class (an object) to be returned.
  • play is a message passed to the new object.
  • object.message
    is the most common syntax in supercollider and is called receiver notation. Other notations are allowed, including normal small talk notation and something called functional notation, which we'll talk about later
  • everything in Supercollider is an object
  • Pbind is part of a set of related classes called patterns

Symbols and Patterns

 Pbind(\dur, 0.5, \freq, 660).play
  • If you don't specify a message to a class, but pass it arguments, it's assumed you're calling new (this is confusing, but you'll see it all the time in help files)
  • Pbinds work by creating a type of object called an event. Events are kind of like the makenote in MAX. It has a bunch of possible parameters and handles a bunch of stuff for you, like computing midinote number based on a supplied frequency or vice versa. It also handles timings by keeping track of server latency (the delay between asking for something to happen and it happening) and sending OSC messages ahead of time
  • Pbinds take as arguments a comma separated list of symbols (which correspond to parameter names) and Patterns (or constant values)

Pfunc

   
 (
  Pbind(
   \dur, 0.4,
   \note, Pfunc({
   
      12.rand
     })
  ).play
 )
  • Pfunc starts with a capital letter, therefore, it must name a class
  • Pfunc is a class, since it's got an argument, there's a constructor being called
  • it takes a function as an argument. functions are declared by code surrounded by curly braces
  • the last line in a function is the value it returns
  • Pfunc is a subclass of Pattern
  • Pbind calls the Pfunc for every event and uses the return value to specify what value goes with \note
  • rand is a message sent to 12, which is an object and an instance of Integer, which is a subclass of SimpleNumber. objects understand all the messages that their class and superclasses understand. When an instance of SimpleNumber (or one of it's subclasses) receives a message of rand, it returns a number between 0 and the object

Declaring Variables and if

 (
  Pbind(
   \dur, 0.4,
   \note, Pfunc({
      var rand_num;
      
      rand_num = 13.rand;
      (rand_num >= 12). if ({
       rand_num = \rest;
      });
      
      rand_num;
     })
  ).play
 )
  • variables are named bits of memory which can store objects. they must be declared. their name must start with a lowercase letter. They only exist within their code block. rand_num only exists in it's Pfunc
  • SuperCollider gives you 26 variables wich it keeps track of all the time. They are named a - z. s holds a pointer to the server
  • = is an assignment so that rand_num holds the result of 13.rand
  • statements have to be separated by semicolons
  • (rand_num >= 12) is an expression which is either true of false. Therefore, it is a type of object called a Boolean. Booleans can take a message of if. If the boolean is true, it will evaluate the function passed as the first argument to if. If it's false, it will evaluate the second function, which is optional. If you don't tell it about second function, it does nothing.
  • \rest tells the Pbind to rest
  • the function returns rand_num, which has in it either an integer or \rest
  • you sometimes also see if with functional notation, which is message(object, arguments) so that would look like
    if((rand_num >= 12), { rand_num = \rest });
    This is not for any particular reason except that visually, it looks more like C or C++

Prout

  
 ( 
  Pbind(
   \dur,  0.4,
   \note,  Prout({
      var bangs, note;
      
      bangs = (4 * 3 * 7 * 2) -1;
      // do full loop twice
     
      bangs.do({ arg index;
    
       note = 10;  
      
       (index % 4 == 0).if ({
        note = note + 5;
       });
       (index % 3 == 0).if ({
        note = note - 3;
       });
       (index % 7 == 0).if ({
        note = note + 6;
       });
      
       note.yield;
      });
    })
  ).play;

  // code stolen from http://www.perl.com/lpt/a/2004/08/31/livecode.html
 )
  • Prouts are like Pfuncs, except they treat their function as a Routine. A Routine is a kind of function which can pause and restart.
  • math operations go form left to right, like a cheap calculator. + and - have the same precedence as * and /, so use parenthesis to specify order of operations
  • // indicates a comment as does /* comment is in between these */
  • giving the message do to an object which is an instance of SimpleNumber causes the function it gets as an argument to be evaluated object+1 times. When it evaluates the function, it gives it an argument the number of iteration it's on, from 0 - object
  • % is modulus, which is the remainder when doing division: 13 % 5 = 3 because 13 = (5 * 2) + 3
  • == tests for equivalency
  • yield is a message which you can pass to any object within a Routine. That object is then returned from the routine, so in this case, Pbind can use it for a note number. When the routine is restarted, it picks up from where it left off.
  • When the Prout eventually runs out, after going through the loop bangs times, it will return nil, which will cause the Pbind to stop playing

Ptpar

 (
 
  Ptpar([0, 
   Pbind(\dur, 0.8,
    \octave, 4,
    \degree, [1, 3, 5],
    \amp, 0.2),
   0, Pbind(\dur, 0.4,
    \octave, 5,
    \degree, Pseq([1, 4, 5, 7, 8, \rest, 5, 7, 5, 5, 6], inf),
    \amp, 0.3)
   ]).play
 )
  • Ptpar takes an array as an argument to it's constructor. Arrays can be declared by putting a list inside square brackets, so [1, 3, 5] is an array. Ptpar's array is made up of pairs of times and patterns. This synchronizes multiple patterns
  • \octave and \degree are just other ways of naming notes
  • if you list an array as one of the value pairs in a pattern, it will create multiple events, one for each item in the array, so the top Pbind sends three sets of OSC messages to the server every time it makes a note
  • Pseq steps through an array, returning each item in turn. It takes two arguments, the first is the array and the second is the number of times to step through it
  • inf is a reserved word which means infinite, so the Pseq never stops stepping through the array. If we gave a 2, instead, it would step through the array twice and then return nil, which will stop the Pbind it is inside, but not the other Pbind, which will keep running forever

Tying things together

 
   (
    Pbind(
     [\freq, \dur], Pfunc({
     
       var pitch, dur;
       
       pitch = (10.rand * 44) + 440;
       if ((pitch < 660), {
        dur = 0.45;
       } , {
        dur = 0.25;
       });
       
       [pitch, dur]
      })
     ).play
   )
  • You can put symbols in arrays and then give them an array of values. The symbol at index n is tied to the value at index n.
  • This lets you tie related parameters together
  • Make sure your Pfunc or Prout or other Pattern returns an array when an array is expected

Accessing Events

  
   (
    Pbind(
    
     \freq, Pwhite(440, 880),
     \dur, Pfunc({arg evt;
          
        (evt.at(\freq) < 660).if ({
         0.45;
        } , {
         0.25;
        });
     })
  ).play
 )
  • Pwhite is a Pattern which returns a value between it's first argument and it's second argument
  • Pfuncs and Prouts can have an argument to their function which is the event as it's been constructed so far. This gives you access to all the parameters which have already been set
  • ifs return the result of the function which they evaluated
  • You can find more Pthings in the help file for Patterns

Array.do and while

   (
    Pbind(
     \dur,  0.25,
     [\freq, \amp], Prout({
     
        var arr, amps;
        
        arr = [];
        amps = [0.2, 0.3, 0.4];
        
        {arr.size < 10}. while({
        
         arr = arr.add((10.rand * 44) + 440);
           arr.do({ arg item, index;
        
          [item, amps.wrapAt(index)].yield;
         })
         
        })
       })
    ).play
   )
  • functions understand the message while. the function is evaluated, if it returns true, the function passed as an argument to the while loop runs once. Then the receiver is evaluated again. This repeats until the receiver evaluates to false.
  • Array.size returns the size of the receiver
  • Array.do is a way to step through an array. The function takes as an argument the array item and the index of the item
  • Array.add is a message which adds an item to an array. Arrays cannot grow in size beyond what they were originally allocated. Therefore, the receiver may be unchanged, although the action returns a new array with the added item. Therefore, if you want to add an item to an array, assign the result to a variable
  • wrapAt is a way to access the contents of the array such, that indexes "wrap around"
    amps.wrapAt(index)
    is equivalent to
    amps.at(index % amps.size)
  • Arrays can be accessed by index

Arrays

Arrays can take other messages, like scramble, which returns a reordered array but does not change the receiver of the message. Choose returns a single randomly chosen item from an array. Many other messages can be found in the helpfiles for Array and it's super classes.

A quick GUI Example

   
   (
   c = Conductor.make({arg thisConductor, arrSize, vol, freq, dur;
    
    vol .spec_(\db);
    freq.spec_(\freq);
   arrSize.sp(10, 1, 50, 1); // sp( val, min, max, step, warp)
    dur.sp(0.45, 0.1, 1, 0);
     
    thisConductor.pattern_(Pbind (
     \db,  vol, 
     \dur, dur,
     \freq, Prout ({
     
         var arr, diff;
        
       arr = [];
       
        
       {arr.size < arrSize.value}. while({
        
        arr = arr.add(freq.value);
          arr.do({ arg item, index;
        
         item.yield;
      });
     });
    })
   ));
  });
  
  c.show;
  )
  • Conductor is a graphical class which gives you some gui widgets. It is included in the Wesleyan build.
  • the first argument is the conductor itself. we can't call it "this" because that's a reserved word
  • the additional arguments are control values, instances of CV. they get GUIS assigned to them usually
  • CV.spec_(\symbol) gives you a predefined ControlSpec which is is designed for a particular type of CV
  • Otherwise, you can define your own. step refers to how big the steps are between values
  • You can give the Conductor a pattern (such as a Pbind) and use the CVs as Patterns
  • If you have to refer to CV in a function or a routine and you want the value that the slider is currently at, use CV.value