Saturday, April 25, 2015

How to keep an installation running on a Raspberry Pi

Previously, I covered, how to keep an installation running. In that post, I wrote about how anything could crash. The Pi is much the same, except everything will crash at some point.

I have been working with SC3.6 (see these build instructions), but this should also be applicable to any other versions of SuperCollider. This is written for Raspbian Wheezy but should work with other versions of Raspbian. It will not work with other Pi operating systems, such as windows, without substantial modifications, especially to the Bash Script

There are a few important differences between running an installation on a laptop and on a Pi.

  • The Pi has fewer resources and crashes much more often and in more ways.
  • The SC server is started separately from sclang
  • Backing up a Pi system is just duplicating an SD card, so anything weird or dangerous you do is much less risky than it would be on your laptop

Getting the Pi Ready

Are you planning on attaching a monitor to the Pi? If so, you will want to tell to boot to the desktop. Otherwise, you want to boot to a command line. You can set these options by running sudo raspi-config. My only experience thus far is with the desktop, because I am also generating some simple graphics. Otherwise, I would definitely boot to the command line. The Pi does not have a lot of processor power and if you're not using the Desktop, you can save some space for installation to run.

Getting your code ready

Efficiency

SC 3.6 on a Pi runs exactly the same code as SC 3.6 on my laptop. Therefore, installation-ready code should run fine on a Pi. There is less memory on a Pi, which may limit the number of Buffers you can have loaded at one time. I have not experienced any issues with running out of memory. The processor, however, does climb to 100% quite easily and this can lead to audio stuttering. Your synthdefs and invocation should be as efficient as possible. If you always fire off three synths at the same time, say, each with a different frequency, you may want to combine them into one single synthdef that takes three frequency arguments. Starting off one more complicated synthdef requires fewer resources than three simpler synthdefs running at the same time.

If you are running the the desktop, you can see how much of the CPU is being used by looking at the monitor box in the upper right hand corner of the screen. If you are running headless and have connected to the Pi via ssh, you can monitor the CPU using the command 'top'. Open a second terminal window, connect to the Pi and type in top -u pi at the command line. The program will tell you the computer's load average and how much of the CPU each program is using.

Within SuperCollider, to find out how much CPU the server is using, Server.default.avgCPU returns a number between 0 and 100. I've used this information to help calculate how many steps to use for a fade in the graphics. A busier CPU means I fade in fewer steps with longer pauses between steps.

Starting the server separately

This is a constraint imposed by the build instructions for 3.6. Right now, we're just going to discuss how this changes our SC code. Fortunately, very little needs changing. Use the same code for booting the server as was covered in the previous tutorial. The error checking is still useful - even if the server is actually already booted.

As the server is actually started outside SuperCollider, the port number for the server can (and will) change. We will tell SuperCollider about the port number via command line arguments - we will tell our program about the port number when we start running it. The arguments will be available to our program in an array: thisProcess.argv. Arguments to our program start at index 0. We need to get this data before we do anything involving the server.


(
var server_port;
// ...
if ((thisProcess.argv.size > 0), {
 server_port = thisProcess.argv[0];
 Server.default = Server(\Raspberry, NetAddr("localhost", server_port.asInteger));
 s = Server.default;
})
// ... Boot or s.waitForBoot only after the above
)

We start by checking if we received any arguments. If the argv.size is 0, then we did not. This means we can still use the same code in the IDE on our regular computer.

The first argument is the very first thing in argv. If there are subsequent arguments, they will also be in argv, in the order they were given.

We create a new server object, called Raspberry, that uses the port we've been given as an argument. Note that we also convert the port to an integer, from a string.

Our new Server object will be our default server, which we also assign to the global s variable. This means everything will work as normal, even on an arbitrary port.

The Bash scripts

Previously, we used a bash script instead of an IDE. We are going to do that again, but now our script is going to do a few extra things - only if we're on a Pi.

Bash scripts always start out with #! at the top of the file. After that line, from then on, anything after a # character is a comment, like anything after // in SuperCollider.

The SuperCollider directory on Pi is either /usr/bin or us/local/bin. to find out which, type which sclang at the command line. If yours is different than the code below, you will need to change it.


#!/bin/bash

port=57110

sleep 20

while true
  do
    #are we on a raspberry pi?
    if [ -f /etc/rpi-issue ]
      then
        # we need these two lines in order to make sound
        export SC_JACK_DEFAULT_INPUTS="system"
        export SC_JACK_DEFAULT_OUTPUTS="system"

        # get rid of anything already running
        killall scsynth
        sleep 2
        killall jackd

        # wait for things to settle down
        sleep 20

        # start jackd
        ( jackd -T -p 32 -d alsa -d hw:0,0 -r 44100 -p 1024 -n3  -s || sudo shutdown -r now ) &

        #wait for jack to settle down
        sleep 10

        # make sure we increment the port every loop
        port=$(( $port + 1))

        # start the server
        /usr/local/bin/scsynth -u $port & # check supercollider's directory!
        server=$!
        sleep 1
        
        # is the server running?
        if kill -0 $server  2> /dev/null
            then
                #all good
                echo "started"
            else
                # try again
                        
                sleep 5
                port=$(( $port + 1 ))                        
                /usr/local/bin/scsynth -u $port & # check supercollider's directory!
                server=$!
                        
                sleep 1

                # is the server running?
                if kill -0 $server  2> /dev/null
                    then
                        #all good
                        echo "started"
                    else
                        sudo shutdown -r now
                fi  
        fi

        sleep 5

        # is the server still running?
        if kill -0 $server  2> /dev/null
            then
                #all good
                echo "still going"
            else
                sudo shutdown -r now
        fi

        /usr/local/sclang installation.scd $port # check supercollider's directory!
    
    else

        /path/to/sclang installation.scd #SuperCollider on your normal computer

    fi


    sleep 1

    killall scsynth

    sleep 1

done


This script is a lot more complex than the last one! This is because more things can go wrong, alas.

We start by initialising a variable and then sleeping for 20 seconds before doing anything. The sleep is there at the start because we are going to start the script whenever the Pi boots. The 20 seconds of waiting around gives time for services to start, like the internet connection, if we have one. Also, it gives us time to open a terminal or a connection and type killall installation.sh if we don't want the installation to actually run.

Then we test if we're on a Pi, by looking to see if a Raspbian-specific file exists. If we are on a Pi, we set two variables that SC3.6 needs set in order to make sound.

Then we kill any servers or versions of jack left running, possibly from previous times through the loop. Then we sleep for 20 more seconds. The Pi is not a fast computer and this gives the system time to settle down after killing jackd, which is the audio server (like CoreAudio) used on Raspbian.

Then we try to start jack up again and run it in the background. If it does not start, we reboot the computer. The default user on a Pi is called 'pi'. By default, that user can sudo without entering a password. We are taking advantage of that to make the system reboot. On a normal computer, it would be risky to leave an installation running that could get superuser privileges so easily. However, the Pi's system is just an SD card, which hopefully we have a spare copy of. If something goes horribly wrong, we can just pop in a new card.

We will configure the Pi so the script runs on startup. This means if the computer reboots, the installation will restart.

Then we wait 10 seconds for jack to settle down.

We then increment the port number. In my testing, many crashes are caused by the server crashing. When it crashes, it may not let go of the port it was using. By incrementing every time, we are less likely to have trouble starting the server.

Then we start the server, wait 1 second and check if it's running. If it's not running, we wait 5 seconds, increment the port and try again. If the second attempt fails, we reboot.

Then we wait five more seconds and check AGAIN if the server is running. This may seem like overkill, but, unfortunately, the system can get into a state where a server will start, but then crash about 2 seconds later. By waiting five seconds, we can make sure the server will actually stay started. If not, we reboot.

Then, finally, we start running the installation program!

Below that, we have an else and the invocation we would use on our other computer. Obviously, this example is slightly silly, as it would make more sense in this case just to have two different scripts. However, if you have helper apps or have to move files around before starting, etc, it can make sense to use the same script for two different systems.

Using helper scripts

If you have a helper script working in the background, it's going to take up some of the processor power of the Pi. However, if you don't mind it running more slowly, you can tell it to use less power. There is a unix command called nice, which you can use when starting your helper script. You give it a number saying how nice a process should be, ranging from -20 to 19. A high number is a very nice process - it becomes more and more sharing (and runs more and more slowly). A negative number is less and less nice. For our installation, we will tell helper scripts to be more nice, but we'll leave sclang and scsynth running normally. (You might want to experiment with making them less nice. I have avoided this out of worry it would interfere with my debugging process.)

You would alter your script to add:


nice -n 10 /path/to/helper/script.sh &
helperpid=$!
sleep 1
/usr/local/sclang installation.scd $port # check supercollider's directory!
sleep 1
killall scsynth
kill $helperpid

Changing the order

What if you want to start the helper script after sclang? We still would want to base the loop on when sclang crashes. Fortunately, we can tell a loop to wait for something to stop (read: crash).


/usr/local/sclang installation.scd $port &# check supercollider's directory!
scpid=$!
sleep 1 # if you want to pause, otherwise, skip this line
nice -n 10 /path/to/helper/script.sh &
helperpid=$!

wait $scpid
sleep 1
killall scsynth
kill $helperpid

Writing Files

To write or not to write? We might want to write files for a few reasons. Perhaps we have a helper script that doesn't understand OSC, or the data we want to pass is particularly large, or a buffer, for example. Or, because on the Pi a server crash means an sclang crash, we may find that our program crashes much more often, so we want to leave notes to ourself, telling the program where it should start up again.

On the other hand, SD cards are not as robust as a normal disk drive. They have a limited number of writes before they die. (Always make a copy of your SD card!) The raspbian operating system does a lot of file writes already. Some Pi users have instructions on how to vastly reduce the number of writes. If you are concerned about the longevity of your SD card, the questions to ask yourself are about budget ('Can I afford the more expensive SD cards that will last longer?' 'How many spares can I keep on hand?') and the installation longevity ('Is this going to run for days or for weeks / months / years? Can I just give a small stack of duplicate SD cards to the organisation running it and will they be able to manage recognising the installation is borked and putting in a new card?'). For what it's worth, I have been using one SD card for more than two weeks of testing with no effort to reduce file writes and doing my own file writing. I have made an image of my SD card and plan to bring a couple of duplicates with me when I set up the installation.

Crash Recovery

Because everything on the installation eventually will crash, you need to keep in mind that a program writing a file will sometimes crash mid-write. Therefore, the program reading the files needs to check if the file exists before opening it, and then, once open, check if it has been corrupted. Note that since the system will sometimes reboot, the writer may have been just partway through writing the file when the system went down.

When my installation is running, I keep my files in /tmp. This is a special directory on Raspbian (and other unix systems, including Mac OS X) that holds files that are meant to be temporary. When the system reboots, /tmp is completely erased. Therefore, when my script starts, I copy files I will need to read and modify to /tmp, so that I know I'm starting with a clean slate after a reboot.

Autostart

I have only done autostarting with a system that boots to a desktop. If you do not, you will need to look into another way of starting. I would suggest picking one that is not tied to logging in. You should be able to open as many or as few ssh connections as you would like, without accidentally firing off many copies of your script.

For Pis that boot to a desktop, you will need to create a file called installation.desktop. Put in it:


[Desktop Entry]
Name=Installation
Exec=/home/pi/installation/installation.sh
Type=application

Change the name and the exec line to match your actual file names and paths. Save that file to ~/.config/autostart/. This is a hidden directory, so if you can't find in your gui, save the file in your home directory and then open a terminal and type mv installation.desktop ~/.config/autostart/ . When your computer boots, it will now automatically start your installation when the desktop opens.

Stopping the installation

If you just want to stop everything, shut down the computer! shudo shutdown -h now But if you want to stop the installation and keep the computer going, this is also possible! You might want to do this because you are updating files or testing. Remember that SD cards are pretty cheap, so if you just need a Raspbian system with SuperCollider on it, you should probably keep that on a separate SD card.

When you log in, the first thing you will want to kill is the bash script that is running everything. Then, you will want to kill sclang, the server and jackd and your helper apps. You can type:


killall installation.sh
killall sclang
killall scsynth
killall jackd

To get a list of everything running, use the ps command: ps -u pi . It will print out a list of everything running that belongs to the user pi (that's us!). The number on the left of the listing is the PID (Processed ID) and the name on the left is the name of the program. If you see your helper script has a PID of 27142, you can kill it directly: kill 27142. If you notice that your programs are resisting your effort to kill them, you can force them to die: kill -9 27142, where the number is what you see when you run ps.

Debugging

In our bash script above, we've got a lot of reboots. This is how the script will look when we deploy it, but not how it should be early in the testing process. We instead want to run the script ourselves, from the command line, so we can see what is being printed out in the way of error codes or from printlns in SuperCollider. Therefore, when first trying this, we would replace all of our 'sudo shutdown -r now' lines with 'exit'. We may need to reboot anyway when the program stops, but this will give a chance to try to read what went wrong. This can also tell us that, say, the server never wants to start, which may indicate that we have not slept for long enough after starting jack.

To launch a script form the command line, cd to the directory that its in and type: ./installation.sh . The ./ at the beginning of the command just says that the script is local to the directory that we're in.

Once the script and program are fairly stable, though, we will want to just leave the installation running, to see if it gets stuck in a state where it stops without restarting or rebooting. When we're ready to do that, we will put the shutdown commands back in and that's when we have the script autostart.

When you've autostarted your program, it runs silently. How can you tell what's going on? It may be helpful to just know what's running at any given time. You can do that via the desktop by running the Task Manager. Or you can to do it from the command line by running top -u pi. This will keep us current on what is running on the system. Is your helper app still going? Is scsynth dying every 10 seconds, causing an endless loop of crashes? Is everything starting when it should and staying alive long enough? This also tells you what is using up the most CPU resources. Does your helper app climb up to 100% before the server crashes?

If you are getting weird crashes that you didn't get when developing the code on your own computer, then one possible problem is the pi version of your startup script. Make a copy of the script and change it so that it always runs as if you are on a Pi. Try it on your own computer (changing the paths as appropriate). Maybe the logic got slightly screwed up.

Maybe your helper script is too nice and the output isn't ready when sclang needs it? Because the Pi has such low power, your program will need to be able to deal with late data. Changing nice values may not be enough to solve this problem. You will need to make sure your SuperCollider program can cope with data arriving late.

Or maybe your helper script isn't nice enough. If it's always at the top in top, it might not be leaving enough space for your server to run. If the server can't get enough CPU, the audio will sound weird and fragmented and the server will crash. This leads to a lot of waiting for everything to restart itself! If you are tending to be using a high percentage of the CPU, scsynth needs to be getting its share of that.

Does your program start out ok, but then seem to always need to reboot in order to recover from a crash? Try making it sleep longer at the end of the loop. Sleeping for several extra seconds in between restarting jack, ssynth and sclang may seem like a long wait between running things, but it's a shorter wait than a reboot.

the aesthetic of glitch

You may want to create a long, lush drone that has a relatively simple synthdef, which just starts and runs without getting messages from sclang. That's not going to crash very often. You will have tons of uptime. Give it a try and see if the Pi will work for you.

Or you might want to start a ton of short, complex synths that rely on doing strange calculations, where the possibility of failure is inherent in the process. (Welcome to my world.) That will definitely crash. There will be long silences while the installation sorts itself out. Therefore, this needs to be a part of your aesthetic that you deal with using some sort of intentionality. You might want to consider having sudden stops and pauses happen when your piece is not crashed, so that a crash just seems like a longer pause. If your piece is using the desktop to do any kind of graphics, set a background image that works within the context of someone experiencing your piece. When the system reboots, people will see that background image, so it should be approached as a part of the installation.

Your audience needs to be prepared for pauses. Foreshadowing in the output is an important part of this, but my last bit of advice is to put something in the program notes, so a person approaching the installation knows it sometimes takes short breaks and may be willing to wait a few minutes if they happen to walk up as it is crashing in a way that will require a reboot.

Conclusion

This document has mostly spoken about the disadvantages of the Pi, sometimes at great length. However, if you are reading this while considering running an installation on the Pi, do not despair. The advantages of the Pi may still be greater than the disadvantages.

  • It is still small and cheap, thus making it ideal for installations (and for shipping in the post).
  • It has a certain coolness factor, which may make venues more interested in your work.
  • An SD card is a stable platform. Once you get an installation working, if you want to run it again a year from now, you don't need to worry overly about system updates, etc. It should just still work.
  • You can do risky things. Again, if you do something really weird that breaks your system, you can just re-image the sd card. Be bold and audacious.

Friday, December 12, 2014

Last Week's Notes

Note: This is here temporarily for the benefit of some students. It will move to Moodle and disappear form here


/*
SuperCollider is an Object Oriented Language that is designed for music and audio. It is a Music V type language. that means, like MAX MSP, the sound generation runs on a different thread than the control stuff. In MAX / MSP, you see this represented by differented coloured patch connections. In SuperCollider, audio runs on a server.

Because we don't have much time, I'm going to ask you take notes and then try stuff out later, not in class.  I also will ask Pieman to put a tutorial up for you on Moodle.

Hopefully you all have SuperCollider installed. When you tried opening it, you saw the screen divided in two parts. One part was the post window, filled with strange text and the other part was just blank and called untitled. The untitled window is where you write your code.  When you want to run your code, you select it with your mouse and hit ctrl-enter if you are in windows or linux, or apple-enter if you are on a mac. On some computers, you may need to use the apple or control key on the right wide of your keyboard. Try this out later with a simple program:
*/

4 + 4

/*
When you highlight 4 + 4 and run it, 8 will print out in your post window.

We're going to start out with working with sound design. Sound runs on the server. Therefore, we need to fist boot the server.  There are a few ways to do this. If you look in the Language menu, one of the items is 'boot server'. Select that.

The text in the little status bar at the bottom should change and turn green. you will also see output in the post window.


Now that the server is booted, let's make some sound:

*/

(
SynthDef(\sine, {
 Out.ar(0,
  SinOsc.ar(440, 0, 0.2)
 )
}).play
)

/*
When you want it to stop, hit ctrl-period or apple-period.

We are defining a SynthDef, sending it to the server and telling it to play. a synthDef is a description of how the server makes a sound.

First it has the name, which starts with a \, then comman curly bracket, then all of the UGens.

Out.ar is where the sound should come out. The channels start with 0, so this is on the left. Next is what should come out.
SinOsc is a sinwav oscillator. It's running at 440 Hz, it's phase is 0, and it's amplitude is 0.2.
Notice that these items have commas between them.

Then we close all of our open parens and curly brackets

Then a .play tells the whole thing to play.


We can organise this better using variables.
A variable is a container with a name. Like a box named sine. It can hold an object.
*/

(
SynthDef(\sine, {
 var sine;
 sine = SinOsc.ar(440, 0, 0.2);
 Out.ar(0, sine);
}).play
)


/*

First we let SuperCollider know that we've got a variable named sine.
We end that with a semicolon, so it knows that statement is done.

Then we do an assignment. Don't read = as 'equals, read it as 'gets'.
sine gets SinOac.ar.  We've got a box named sine. We've put a sine oscillator in the box, to keep track of it.
Then a semi colon, so it knows we're done with the assignment.
Then Our.ar, saying what channel to go out on.  And we tell it, put out whatever's in sine. Take the contents of the box and send it to the left speaker.


Every line needs to be separated by semicolons. Later on, when you can't figure out why something isn't working, check the semi colons first. It's the most common mistake. I still forget them all the time.


These sines all last forever, so let's try an envelope:

*/


(
SynthDef(\sinenv, {
 var sine, env;
 sine = SinOsc.ar(440, 0, 0.2);
 env = EnvGen.kr(Env.perc, doneAction:2);
 Out.ar(0, sine * env);
}).play
)


/*
we have added an extra variable called env
We've put an EnvGen into it. env gets EnvGen.kr.  An EnvGen is an envelope generator. First we tell it what envelope shape, then we do this doneAction:2 thing.

When we're telling out what to play, we tell it to multiple the sine box by the env box.


Let's do some ring modulation:

*/

(
SynthDef(\sinerm, {
 var sine, ringmod, env;
 sine = SinOsc.ar(440, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 env = EnvGen.kr(Env.perc, doneAction:2);
 Out.ar(0, ringmod * env);
}).play
)


/*

We've added a new variable called ringmod.

Ring mod gets a new sinosc times the contents of the sine variable.
When SuperCollider is looking at your code and it sees an assignment - the equals sign - it does everything on the right hand side of the assingment and puts the result of everything into the variable on the left.

We've picked 30 Hz, because that's the frequency daleks use, but what if we want different frequencies?

We could keep changing the synthdef:
*/

(
SynthDef(\sinerm, {
 var sine, ringmod, env;
 sine = SinOsc.ar(470, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 env = EnvGen.kr(Env.perc, doneAction:2);
 Out.ar(0, ringmod * env);
}).play
)

(
SynthDef(\sinerm, {
 var sine, ringmod, env;
 sine = SinOsc.ar(810, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 env = EnvGen.kr(Env.perc, doneAction:2);
 Out.ar(0, ringmod * env);
}).play
)

(
SynthDef(\sinerm, {
 var sine, ringmod, env;
 sine = SinOsc.ar(752, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 env = EnvGen.kr(Env.perc, doneAction:2);
 Out.ar(0, ringmod * env);
}).play
)

/*

Or, we could use a special kind of variable called an argument

*/

(
SynthDef(\sinermarg, { arg freq;
 var sine, ringmod, env;
 sine = SinOsc.ar(freq, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 env = EnvGen.kr(Env.perc, doneAction:2);
 Out.ar(0, ringmod * env);
}).add
)

/*

We've declared the argument at the top. That uses the keyword arg. argument need to go before variables.
So first you have the SynthDef and the name of it and the culry bracked.
Then next is arguments, starting with the keyword arg.
then are the variables, starting with the keyword var.
Everything else is below that.


It does not make sound when we play this one, because we changed the .play at the end to .add.

Instead of playing this right away, we tell the server to remember it, so we can play it later.

*/

Synth(\sinermarg, [\freq, 440])


/*
there are a lot of ways we can play a SynthDef, and one of them is by via Synth.

The first argument to Synth is \sinermarg, which is the name of the synthdef.  The next is this square bracket thing.

The first thing in the square brackets is \freq, this is the name of our argument, but starting with a slash.
Then is the value we want to give it.

*/


Synth(\sinermarg, [\freq, 860])

/*

What if we want to have more arguments?

*/

(
SynthDef(\sinermargs, { arg freq, rm, dur, amp;
 var sine, ringmod, env;
 sine = SinOsc.ar(freq, 0, amp);
 ringmod = SinOsc.ar(rm) * sine;
 env = EnvGen.kr(Env.sine(dur), doneAction:2);
 Out.ar(0, ringmod * env);
}).add
)

Synth(\sinermargs, [\freq, 912, \rm, 123, \dur, 7, \amp, 0.3])



/*

For this morning, we're going to focus more on SynthDefs, so we'll get to do sequences of notes in the afternoon.


Let's try different waves, panned to centre
*/


(
SynthDef(\saw, { arg freq, dur, amp;
 var saw, env, panner;
 saw =  Saw.ar(freq, amp);
 env = EnvGen.kr(Env.sine(dur), doneAction:2);
 panner = Pan2.ar(saw, 0,env);
 Out.ar(0, panner);
}).add
)

Synth(\saw, [\freq, 550, \dur, 3, \amp, 0.2])


/*

Notice that saw and sign have different arguments in their list. the second thing in the list to sign is a 0 and the amplitude is third. But saw just goes directly from frequency to amplitude.

How would you know about how these are different (or even what the various oscillators are)?  the answer is in the help browser!

Under the Help menu, there is a 'show help browser' option. You can click on search there to start searching. Or just browse around. Get to know the help browser. It is your friend. There are lots of examples.

Let's try some filtered noise:

*/


(

SynthDef(\subtractive, { arg freq, dur, amp;
 var noise, filt, env, panner;
 noise = WhiteNoise.ar;
 filt = RLPF.ar(noise, freq);
 env = EnvGen.kr(Env.sine(dur), doneAction:2);
 panner = Pan2.ar(filt, 0,env * amp);
 Out.ar(0, panner);
}).add
)

Synth(\subtractive, [\freq, 1200, \dur, 3, \amp, 0.2])


/*
WhiteNoise is just noise, so there's no need to give it additional information
RLPF is a Resonant Low Pass Filter

You may have noticed some of these are .ar and some are .kr.
ar is audio rate
kr is control rate

The envelope doesn't need to change values as often as a 440 Hz sine oscillator in order to sound good. The oscillator needs to go through it's entire wave form 440 times a second, whereas, the envelope needs much less precision.

*/


/*

This file is a SuperCollider file.  The english text is in a comment.  Programmers sometimes want to put human-language notes in their files.  This makes the files more readable to humans, but the text is not meaningful to SuperCollider. They mark the text as not being code by putting comment markers around it. Every one of these block of text starts with a slash star and ends with a star slash.
*/


/* <-- this is the starts of a comment
this is the end of a comment ---> */

// <- this marks a comment that only lasts for one line (and thus does not need an end marker)




Pbind Notes from this morning

Note: This is here temporarily for the benefit of some students. It will go up on Moodle and then be removed from here.


/*

Now what we want is a way to play notes in sequence. Let's use an earlier synthdef:

*/

(
SynthDef(\sin, { arg freq = 440, dur = 1, amp= 0.5, pan=0;
 var sine, env, panner;
 sine = SinOsc.ar(freq, 0, amp);
 env = EnvGen.kr(Env.perc(0.01, dur), doneAction:2);
 panner = Pan2.ar(sine, pan, env);
 Out.ar(0, panner);
}).add
)


(

Synth(\sin, [\freq, 440, \dur, 1, \amp, 0.3]);
Synth(\sin, [\freq, 550, \dur, 1, \amp, 0.3]);
Synth(\sin, [\freq, 660, \dur, 1, \amp, 0.3]);

)

/*

This plays all at once!

Fortunately, there are several ways to add timings.  One of these is Pbind.

*/


Pbind.new.play


/*

As before, press cntl-. or apple-. to stop

Pbind makes a LOT of assumptions on your behalf, so you can run it with no arguments at all.  Or you can specify as much as you want.

Let's tell it to use our synthdef:

*/

(

Pbind(
 \instrument, \sin
).play


)


/*

Let's tell it to play louder

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5
).play


)


// Change the freq and dur

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \freq, 770,
 \dur, 0.5
).play


)

/*

  This is better, but let's make the notes change over time


*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \freq, Pseq([440, 550, 660, 770, 880], 1),
 \dur, 0.5
).play


)


/*

Pseq plays all the array contents in order, then repeats the number of times listed

*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \freq, Pseq([440, 550, 660, 770, 880], 3),
 \dur, 0.4
).play


)


/*
To repeat forever, use the keyword inf

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \freq, Pseq([440, 550, 660, 770, 880], inf),
 \dur, 0.2
).play


)


/*


We can use Pseqs for ANY item in a Pbind

*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \freq, Pseq([440, 550, 660, 770, 880], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play


)


/*

The first Pseq that ends, ends the entire Pbind



Instead of specifying frequencies let's switch to scale degrees

*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \degree, Pseq([0, 2, 4, 6, 7], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play


)



/*

The Pbind does the maths for us to map the scale degrees to the frequency.  This works because our synth uses the argument freq for frequency. the Pbind assumes this is what the frequency argument will be called and acts accordingly.


We can change to a different number of scale steps

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \stepsPerOctave, 10,
 \degree, Pseq([0, 2, 4, 6, 7], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play


)



/*

We can change scales

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Pseq([0, 2, 4, 6, 7], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play

)
/*

Look up scales in the help browser for more

(incidentally, when you want to search, the search box at the top right searches only the currently displayed page.  to search everything, click on the word 'search' on the top.



We can do maths with Pseqs

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Pseq([0, 2, 4, 6, 7], inf) + Pseq ([0, 0, 0, 0, 0, 5, 5, 5, 5], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10)
).play

)


/*

We can nest Pseqs

*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Pseq([0, 2, 4, 6, 7], inf) +
 Pseq ([
  Pseq([0], 5),
  Pseq([5], 4)
 ], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10)
).play

)

/*

They can also be multiplied, divided, etc.



We can play multiple notes at once:

*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Pseq([[1, 3], [3, 5, 7], [2, 5], 7, [1,8]], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play

)


/*

If a pseq gets an array back for any value, it will make as many notes as are in the array, each with one of the values in the array.

So when it gets [3, 5, 7] for the degree, it makes one note with the 3rd, one with the 5th and one with the 7th.




We can not always use direct sequences

*/


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7], 3),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play

)


/*

Prand picks an item randomly from the array, the amount of times you tell it to. So rather than playing through everything 3 times, it picks 3 items

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2)
).play

)


/*

Prands can do all the same maths as Pseqs and work in combination with them

*/




(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7], inf)  + Pseq ([0, 0, 0, 0, 0, 5, 5, 5, 5], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10)
).play

)

(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7, [1, 3, 5]], inf)  + Pseq ([0, 0, 0, 0, 0, 5, 5, 5, 5], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10)
).play

)



(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7, [1, 3, 5]], inf)  + Pseq ([
  Pseq([0], 5),
  Pseq([5], 4),
  Prand([0, 2, 5], 1)
 ], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10)
).play

)

/*

Pwhite varies randomly between a low and a high number

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7, [1, 3, 5]], inf)  + Pseq ([
  Pseq([0], 5),
  Pseq([5], 4),
  Prand([0, 2, 5], 1)
 ], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1, 1, inf)
).play

)

/*
The panning is hard left, hard right or center, because it's only picking whole number. If we want it to pick numbers between the whole numbers, we need to specify that

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Prand([0, 2, 4, 6, 7, [1, 3, 5]], inf)  + Pseq ([
  Pseq([0], 5),
  Pseq([5], 4),
  Prand([0, 2, 5], 1)
 ], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1.0, 1.0, inf)
).play

)




/*

The whole numbers are called intergers and the numbers with decimal points are called floating point or floats, which are terms you might see in the help files.

*/

/*

This behaviour is actually useful, if we want to pick scale degrees, we may not what to pick ones in between

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Pwhite(0, 8, 10),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1.0, 1.0, inf)
).play

)


(

Pbind(
 \instrument, \sin,
 \amp, 0.5,
 \scale, Scale.minor,
 \degree, Pwhite(0.0, 8.0, 10),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1.0, 1.0, inf)
).play

)

/*

What if we want moments of silence?

we use the symbol \rest for the freq or degree

*/

(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Pseq([0, 2, \rest], 3),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1.0, 1.0, inf)
).play
)
(

Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Prand([1, 3, 5, 7, 8, [1, 3, 5], \rest], inf)  + Pseq ([
  Pseq([0], 5),
  Pseq([5], 4),
  Prand([0, 2, 5], 1)
 ], inf),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1.0, 1.0, inf)
).play

)

/*

What if we want to do one thing and go on to another thing?

*/

(
Pseq([
 Pbind(
  \instrument, \sin,
  \amp, 0.3,
  \scale, Scale.minor,
  \degree, Prand([1, 3, 5, 7, 8, [1, 3, 5], \rest], inf)  + Pseq ([
   Pseq([0], 5),
   Pseq([5], 4),
   Prand([0, 2, 5], 1)
  ], inf),
  \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 2),
  \pan, Pwhite(-1.0, 1.0, inf)
 ),
 Pbind(
  \instrument, \sin,
  \amp, 0.3,
  \stepsPerOctave, 10,
  \degree, Pwhite(1, 10, 10),
  \dur, Prand([0.4, 0.6, 0.8], inf)
 )
]).play
)

(
Pseq([
Pbind(
 \instrument, \sin,
 \amp, 0.3,
 \scale, Scale.minor,
 \degree, Pseq([0, 2, \rest], 3),
 \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
 \pan, Pwhite(-1.0, 1.0, inf)
),
 Pbind(
  \instrument, \sin,
  \amp, 0.3,
  \stepsPerOctave, 10,
  \degree, Pwhite(1, 10, 10),
  \dur, Prand([0.4, 0.6, 0.8], inf)
 )
]).play


/*

What if we don't just want chords, but want two streams going at once?

*/
(
SynthDef(\crapSnare, { arg amp, pan;
 var noise, env, panner;

 noise = WhiteNoise.ar;
 env = EnvGen.kr(Env.perc, doneAction:2);
 panner = Pan2.ar(noise, pan, env);
 Out.ar(0, panner * amp)
}).add
)

(
Ppar([
 Pbind(
  \instrument, \sin,
  \amp, 0.3,
  \scale, Scale.minor,
  \degree, Prand([1, 3, 5, 7, 8, [1, 3, 5], \rest], inf)  + Pseq ([
   Pseq([0], 5),
   Pseq([5], 4),
   Prand([0, 2, 5], 1)
  ], inf),
  \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
  \pan, Pwhite(-1.0, 1.0, inf)
 ),
 Pbind(
  \instrument, \crapSnare,
  \amp, 0.2,
  \dur, 0.8
 )
]).play
)

// note the snare never stops!

// or moving the snare to the offbeat:




(
Ppar([
 Pbind(
  \instrument, \sin,
  \amp, 0.3,
  \scale, Scale.minor,
  \degree, Prand([1, 3, 5, 7, 8, [1, 3, 5], \rest], inf)  + Pseq ([
   Pseq([0], 5),
   Pseq([5], 4),
   Prand([0, 2, 5], 1)
  ], inf),
  \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
  \pan, Pwhite(-1.0, 1.0, inf)
 ),
 Pbind(
  \instrument, \crapSnare,
  \amp, 0.2,
  \dur, Pseq([0.4, Pseq([0.8], inf)], 1)
 )
]).play
)


/*
to get rid of the first beat, we need to give it a rest for the freq.
It does not matter that the synthDef doesn't take freq as an argument. This is just for the safe of the pbind

*/


(
Ppar([
 Pbind(
  \instrument, \sin,
  \amp, 0.3,
  \scale, Scale.minor,
  \degree, Prand([1, 3, 5, 7, 8, [1, 3, 5], \rest], inf)  + Pseq ([
   Pseq([0], 5),
   Pseq([5], 4),
   Prand([0, 2, 5], 1)
  ], inf),
  \dur, Pseq([0.2, 0.2, 0.4, 0.2, 0.6], 10),
  \pan, Pwhite(-1.0, 1.0, inf)
 ),
 Pbind(
  \instrument, \crapSnare,
  \amp, 0.2,
  \dur, Pseq([0.4, Pseq([0.8], inf)], 1),
  \freq, Pseq([\rest, Pseq([440], inf)], 1),
 )
]).play
)

/*

Ppars can also be nested in Pseqs.
The helpfiles for streams patterns and events go into loads of detail about how to use p-things


When you are making your own pbinds, watch out for forgetting commas in the middle of them, or dangling commas at the end. commas should go between items.

*/


/* Finally, we might have a synth that lasts for a really long time, that we want to change, but not end
*/

(
SynthDef(\sinerm, {arg freq, amp;
 var sine, ringmod, env;
 sine = SinOsc.ar(freq, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 Out.ar(0, ringmod * amp);
}).play
)

(

Pmono(\sinerm,
 \degree, Pwhite(0, 12, inf),
 \amp, 0.5,
 //\stepsPerOctave, 10,
 \db, -3
).play

)


// We can change the synthdef a bit to add some lag

(
SynthDef(\sinermlag, {arg freq, amp;
 var sine, ringmod, env, lag;
 lag = Lag.kr(freq);
 sine = SinOsc.ar(lag, 0, 0.2);
 ringmod = SinOsc.ar(30) * sine;
 Out.ar(0, ringmod * amp);
}).play
)



(

Pmono(\sinermlag,
 \degree, Pwhite(0, 10, inf),
 \amp, 0.5,
 \stepsPerOctave, 10,
 \db, -3 // Pmono and Pbind will translate db to amp for us
).play

)


/*

Algorave:  uses algorythms to generate dance music
Can be live-coded, but need not be

Examples:

Microrex https://soundcloud.com/micorex
Yee-king http://open.spotify.com/track/1UTiLOe4LwarRDWR5pmjZg
Norah Lorway http://earthrid.bandcamp.com/album/ive-had-dreams-like-this

*/

Some Notes for Students Today

Note: This is literally for some students I have today. It will appear on Moodle some time soon and then disappear from this space.


/*

Extra Things!



Q: How do you make a Pmono stop?

A: A Pmono stops when it runs out of values, to send, just like a Pbind. However, unless we tell a synth to stop, it will keep going forever.



Q: How do you tell a synth to stop?

A: Envelopes!  We've seen this already with fixed duration envelopes. But if we don't know from the start how long we want the synth to go for, when need to use a different kind of envelope.

For example, and ASR.

ASR stands for 'attack, sustain, release and is a very common envelope in electronic music.  It uses a 'gate' to know when to start and stop. When the gate is 1, the envelope starts doing the attack, then it sustains indefinitely. As soon as the gate changes to 0, it immeditately stops whaever its doing to do the release portion.

Ex:

*/
(
SynthDef(\blipGate, {|freq, amp, gate=1, pan|  // the vertical bars are equvalent to using the keyword arg
 var blip, panner, env;
 blip = Blip.ar(freq, 10, 0.2);
 env = EnvGen.kr(Env.asr, gate, doneAction:2); // look at the helpfile for Env for more
 panner = Pan2.ar(blip, pan, env);
 Out.ar(0, panner * amp);
}).add
)

(
Pmono(
 \blipGate,
 \pan, Pwhite(0.0, 1.0, 5),
 \degree, Pwhite(1, 4, 5)
).play
)


// gates also work with Pbinds

(
Pbind(
 \instrument, \blipGate,
 \pan, Pwhite(0.0, 1.0, 5),
 \degree, Pwhite(1, 4, 5)
).play
)

/*

Q: How can I make frequency changes less abrupt?

A:  Add in some lag:

*/

(
SynthDef(\blipLag, {|freq, amp, gate=1, pan|  // the vertical bars are equvalent to using the keyword arg
 var lag, blip, panner, env;
 lag = Lag.kr(freq);
 blip = Blip.ar(lag, 10, 0.2);
 env = EnvGen.kr(Env.asr, gate, doneAction:2); // look at the helpfile for Env for more
 panner = Pan2.ar(blip, pan, env);
 Out.ar(0, panner * amp);
}).add
)

(
Pmono(
 \blipLag,
 \pan, Pwhite(0.0, 1.0, 5),
 \degree, Pwhite(1, 4, 5)
).play
)




/*

Q: If you're live coding and want to change a pbind without stopping it, can you do that?

A: Yes, with Pdef

*/

(
Pdef(\blips,
 Pbind(
  \instrument, \blipGate,
  \pan, Pwhite(0.0, 1.0, inf),
  \degree, Pwhite(1, 4, inf),
  \dur, 0.4
 )
).play
)

// just put a Pdef around the pbind



// now, let's change without stopping

(
Pdef(\blips,
 Pbind(
  \instrument, \blipGate,
  \pan, Pwhite(0.0, 1.0, inf),
  \degree, Pwhite(1, 4, inf),
  \dur, Prand([0.4, 0.2, 0.2, 0.8], inf)
 )
).play
)


// another change

(
Pdef(\blips,
 Pbind(
  \instrument, \blipGate,
  \pan, Pwhite(0.0, 1.0, inf),
  \degree, Pwhite(1, 4, inf) * Pwhite(1, 2),
  \dur, Prand([0.4, 0.2, 0.2, 0.8], inf)
 )
).play
)



Pdef(\blips).fadeTime = 5; // tell it to fade changes in and out over 5 seconds

Pdef(\blips).stop; // tell it to stop (but don't stop anything else, including other Pdefs)


/*

Q: what are some good oscillators?

A:

SinOsc
Blip
Formant
Pulse
Saw
Klank

// look at the help files for those


Ok, now I want you folks to get to work making some sounds and patterns. I'll come around to answer questions, or you can pair up if you want.

*/



Thursday, May 22, 2014

How to keep an installation running

Writing a program that can make it through the length of a single 20 minute performance can sometimes be challenging, but installation code needs to run for a full day or sometimes weeks and keep going. The first step, of course, is to make your code as bug-free as possible. However, even in this case, your code will eventually crash, though no wrong doing of your own. Therefore, the thing to do is to recover gracefully.

The latest versions of SuperCollider are actually three programs running at once - the IDE; sclang, the language; and scserver, the audio server. Any one of these things can crash.

SkipJack

SkipJack is an object that is like a Task, except that it survives cmd-period. It takes several arguments, the first of which is a function and the second of which is a number indication the wait time in between executions of the function.

SkipJack({"SkipJack".postln}, 2)

This code would print out SkipJack once every two seconds. The advantage of using SkipJack in this case is not that it will keep going after a comand-periiod, but rather that it's on a different thread than the rest of your program. If your main execution loop gets stuck some place and effectively dies, SkipJack will likely carry on. Therefore, we can use SkipJack to check on your main loop and try to revive it.

Sclang

How can we tell your main thread is still running without also stopping if it stops? One way to check is by looking at a shared variable. Let's have a number. Every time we go through the loop, we can set it to 3. Meanwhile, SkipJack could be running with a duration roughly equivalent to how long it should take to get through our loop. It could subtract one from our number. If our main execution loop stops, that number will count down towards zero and then go negative.


var alive, click, dur, task;

dur = 2;
click = { alive = 3 };

task = Task({
inf.do({

"still allive".postln;
click.value;
dur.wait;
})
}).play;

SkipJack({
"are we alive?".postln;
(alive <=0).if({
task.resume;
task.play;
"play!".postln;
(alive <= -2).if({
1.exit;
})
});
alive = alive -1;
}, dur);

If alive gets to zero, first we try to get the task running again. This sometimes works. If it fails, however, we call 1.exit, which causes all of sclang to exit. If we can't recover inside sclang, we can recover outside it.

The Server

We'll need a separate loop to check on the server.


SkipJack({
Server.default.boot(onFailure:{});
Server.default.doWhenBooted({}, onFailure:{1.exit});

}, dur);

This may look odd because it changes the 'onFailure' argument, but the effect of it is that if the server is not already booted, it will take a moment and may struggle to boot. If it fails, all of SuperCollider exits.

Keeping a Synth Alive

If your loop is firing off new Synths, you don't need to worry about whether each individual synth keeps going, but if you're just changing parameters on an individual synth that keeps running, you also need to watch out for it perishing. there are a few ways to do this. Maybe you want to check if it has gone silent?


(
var syn, lastrms, max_silence;

SynthDef(\stereoListenForSilence, {|in=0, out=0|
var input;
input = In.ar(in, Server.default.options.numOutputBusChannels);
SendPeakRMS.kr(input, 1, 3, '/loudnessMonitoring'); // send the RMS once per second
ReplaceOut.ar(0, LeakDC.ar(input).tanh); // Optional line to get rid of offset and peaking
}).add;

/* ... */


Synth(\stereoListenForSilence, nil, RootNode(s), \addToTail);

max_silence = 10; // 10 seconds


lastrms=Array.fill(max_silence, {1});

osc_listener = OSCFunc({ |msg|
var rms;
rms = msg[4].asFloat.max(msg[6].asFloat);
lastrms.removeAt(0);
lastrms.add(rms);
(lastrms.sum <= 0.0001).if ({
"too quiet".postln;
// retsart your synths
s.freeAll;
Synth(\myAmazingSynthDef);
Synth(\stereoListenForSilence, nil, RootNode(s), \addToTail);
});
}, '/loudnessMonitoring');


You can put a monitoring Synthdef on the server's root node and use SendPeakRMS to send OSC messages with the overall amplitude of all running synthdefs. Then, set up an OSCFunc to check if the peak amplitude has been near zero for too long. If it has, free everything and put up new synths. This will not tell you if your server freezes or if your monitoring synth stops sending OSC messages.

Or if you just want to check if an individual Synth is still running, you can use OSCFuncs and SkipJack together.


(

var syn_alive, dur;

dur =1;
syn_alive = 3;

SynthDef(\myAmazingSynthDef, {

var sin, trig;
SendTrig.kr(Impulse.kr(dur.reciprocal));
sin = SinOsc.ar;
Out.ar(0, sin);

}).add;


/* ... */

Synth(\myAmazingSynthDef);

OSCFunc({ arg msg, time;
syn_alive = 3;
},'/tr', s.addr);


SkipJack({
(syn_alive <=0).if({

s.freeAll;
Synth(\myAmazingSynthDef);

(syn_alive <= -2).if({
1.exit;
})
});
syn_alive = syn_alive -1;
}, dur);


SkipJack({
Server.default.boot(onFailure:{});
Server.default.doWhenBooted({}, onFailure:{1.exit});

}, dur);
)

Try quitting the Server via the gui and everything gets back to where it was in under 3 seconds.

No IDE

Previously in this document, we've intentionally made sclang crash, which, if you're running the IDE, is no fun. However, we will not be running sclang through the IDE. Instead, we'll run it from a BASH script. On your computer (if you have a mac or unix), when you open a terminal, what's running in it is a Bash shell. You can write scripts for this shell, which you edit in a plain text editor.


#!/bin/bash


while true
do

/path/to/sclang installation.scd

sleep 1

killall scsynth

sleep 1

done


Ok, so first things first, open a terminal and type:

which bash

It may be /bin/bash or /usr/bin/bash. It's what you want in the first line of the bash script. So if you get back /usr/bin/bash, change the first line to #!/usr/bin/bash.

To find the path to sclang, you can try typing 'which sclang', but if you're on a mac, this is not going to work. Instead, you will need to find the SuperCollider application on you hard drive. Right click on it to examine package contents. If you poke around in there in folders called things like Resources or MacOs, you will eventually find sclang. Drag that file to your SuperCollider IDE to find out the path for it. Put that path into your bash script in the place of '/path/to/sclang'.

Save the script as installation.sh and save your supercollider file as installation.scd. Put them together in the same folder or directory. In your terminal window, cd to that directory and type:

chmod +x installation.sh

Or, alternately, if you'd prefer to use a GUI, get information about the installation.sh file and click a checkbox to make it executable.

What this script does is loop forever. It runs your program. When your program exists, it kills whatever server might still be running and then restarts your program. If your program crashes out entirely, this script will restart it.

Helper Apps

If your installation relies on a helper application, like let's say you're using PD to manage HID communications, you'll want that in your loop also, but as both applications are running at the same time, you'll need to run the helper application in the background, but keep track of the PID so you can kill it when your program crashes.


#!/bin/bash


while true
do

/path/to/pd hid_helper.pd &
pid=$!
/path/to/sclang installation.scd

sleep 1

killall scsynth
kill $pid

sleep 1

done

Make sure your hid_helper is in the same folder as as your other files. Find the path to pd in the same way you got the path to sclang. The & makes it run in the background and the next line tracks the PID, so you can kill it later.

Obviously, you'll also want to keep track of your helper application, which you can do via OSC in the same way you keep track of your synthdefs. If your helper application quits, you'll need to do a 1.exit to force the bash script to restart everything.

Making it all go

This is an installation, so if you're using your own laptop, don't run it as yourself. Make a new user account and don't give that user administrative rights. Make all your files READABLE by that user (or everyone on the system), but don't give that user write access. Set the system so that when it boots up, it automatically logs in as that user.

Log in to the system as the new user. Go to the settings and say you want to autostart an application on login. The application you want to autostart is installation.sh

Try booting your computer. Does it start your installation? Once you've got that sorted out, leave it running in your living room for days and days until you think you're losing your mind. Every so often, use the process manager to kill the server or the helper application or wiggle a wire or otherwise create a bit of problems and see if your installation survives.

Wednesday, October 13, 2010

Getting Started with BBCut2

Installing

BBCut2 is a nifty library for doing break beat cutting. To use it, you must first install it. It is not available as a quark, alas. To get the library, download it from http://www.cogs.susx.ac.uk/users/nc81/bbcut2.html. Then unzip it. Inside, you will find several directories.

  1. Move the "bbcut2 classes" directory to ~/Library/Application\ Support/SuperCollider/Extensions . That tilda represents your home directory, so if your user name is nicole, you would put the file in /Users/nicole/Library/Application\ Support/SuperCollider/Extensions . If the Extensions directory does not exist on your system, then create it.
  2. Put the "bbcut2 help" directory inside the classes directory that you just moved.
  3. Put the "bbcut2 ugens" directory in ~/Library/Application\ Support/SuperCollider/Extensions/sc3-plugins . If this directory does not exist, then create it.
  4. Take the contents of the "bbcut2 sounds" directory and put them in the sounds folder with your SuperCollider application. so if you have SuperCollider in /Applications, you would put the contents of "bbcut2 sounds" in /Applications/SuperCollider/sounds

Then, re-start SuperCollider. Depending on what version of SC you have, you may have duplicate classes. If you do, there will be errors in the post window. If you see that this is a problem for you, go find the files in the BBCut classes and delete them, making sure to keep the other copy. The error message will tell you where to find the files and which ones they are.

The Clock

BBCut relies on a clock. When I'm coding, I usually base the clock off the default clock:

 TempoClock.default.tempo_(180/60);
 clock = ExternalClock(TempoClock.default); 
 clock.play;  

The tempo is defined as beats per second. That's beats per minute, divided by 60 seconds. In the above example, the clock rate is 180 bpm, which is then divided by 60 to set the tempo. If you wanted a clock that was 60 bpm, you would set tempo_(60/60), or for 103 bpm, it would be tempo_(103/60)

BBCut uses an ExternalClock, which uses a TempoClock, so in the above example, I give it the default TempoClock. I don't have to use the default one, but could declare a new one if I wanted: clock = ExternalClock(TempoClock(182/60));

The next step is to the clock to play. If you forget this step (which I often do), nothing happens later on. Telling the clock to play is important. BBCut relies on this clock.

Working with Buffers

There is a special sort of buffer used by BBCut, called a BBCutBuffer. The constructor for this takes two arguments. The first is a string which should contain the path and file name of the file. The second argument is the number of beats in the file. For example, we could open one of the sound files that came with BBCut:

 sf= BBCutBuffer("sounds/break",8);

We need to wait for the Buffer to load before we can start using it. One way to do that is to put the code that relies on the Buffer into a Routine. And then, we can tell the Routine to wait until the server is ready to carry on.

 sf= BBCutBuffer("sounds/break",8);

 Routine.run({
  s.sync; // this tells the task to wait
  // below here, we know all out Buffers are loaded
   . . .
 })

Now we can tell BBCut that we want to cut up a buffer and get it to start doing that.

  cut = BBCut2(CutBuf3(sf)).play(clock);

BBCut2 is the class that runs everything, so we make a new one of these. Inside, we pass a CutBuf, which is a class that handles Buffer cutting. We tell the BBCut2 object to play, using the clock. This starts something going.

Cutting is much more interesting if it can jump around in the buffer a bit:

  cut = BBCut2(CutBuf3(sf, 0.4)).play(clock);

We can specify the chances of a random cut. 0.0 means a 0% chance and 1.0 is a 100% chance. We can set the chances at any numbers between and including 0.0 to 1.0. If we want a 40% chance of a random jump, we would use 0.4.

Cut Procedures

We can tell BBCut to use one of several cut procedures. The original one is called BBCutProc11.

  cut = BBCut2(CutBuf3(sf, 0.4), BBCutProc11.new).play(clock);

It can take several arguments, which are: sdiv, barlength, phrasebars, numrepeats, stutterchance, stutterspeed, stutterarea

  • sdiv - is subdivision. 8 subdivsions gives quaver (eighthnote) resolution.
  • barlength - is normally set to 4 for 4/4 bars. If you give it 3, you get 3/4
  • phrasebars - the length of the current phrase is barlength * phrasebars
  • numrepeats - Total number of repeats for normal cuts. So 2 corresponds to a particular size cut at one offset plus one exact repetition.
  • stutterchance - the tail of a phrase has this chance of becoming a repeating one unit cell stutter (0.0 to 1.0)

For more on this, see the helpfile. In general, the cut procedures are very well documented. Here's an example of passing some arguments to BBCutProc11:

  cut = BBCut2(CutBuf3(sf, 0.4), BBCutProc11(8, 4, 2, 2, 0.2).play(clock)

We can tell the cutter to stop playing, or free it

  cut.stop;
  cut.free;

Putting all of what we have so far together, we get:

(
 var clock, sf, cut;
 
 TempoClock.default.tempo_(180/60);
 clock = ExternalClock(TempoClock.default); 
 clock.play;
 
 sf= BBCutBuffer("sounds/break",8);

 Routine.run({
  s.sync; // this tells the task to wait

  cut = BBCut2(CutBuf3(sf, 0.4), BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);

  30.wait; //  // let things run for 30 seconds
  
  cut.stop;
  cut.free;
 })
)

There are several other cut procedures, like WarpCutProc1 or SQPusher1 or SQPusher2. If you go look at the main helpfile, you can find which ones are available. This file is called BBCut2Wiki (and is found at ~/Library/Application\ Support/SuperCollider/Extensions/bbcut2\ classes/bbcut2\ help/BBCut2Wiki.help.rtf or by selecting the text BBCut2Wiki and typing apple-d )

Polyphony

The clock keeps things in sync, so you can run two different cut procedures at the same time and have things line up in time.

  cut1 = BBCut2(CutBuf3(sf, 0.4), BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);
  cut2 = BBCut2(CutBuf3(sf, 0.2), WarpCutProc1.new).play(clock);

You can even mix and match sound files:

(
 var clock, sf1, sf2, cut1, cut2, group;
 
 TempoClock.default.tempo_(180/60);
 clock = ExternalClock(TempoClock.default); 
 clock.play;
 
 sf1= BBCutBuffer("sounds/break",8);
 sf2= BBCutBuffer("sounds/break2",4);

 Routine.run({
  s.sync; // this tells the task to wait

  cut1 = BBCut2(CutBuf3(sf1, 0.4), BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);
  cut2 = BBCut2(CutBuf3(sf2, 0.2), WarpCutProc1.new).play(clock);

  15.wait;
  cut1.stop;
  cut2.stop;
 })
)

If you want to also sync up a Pbind, you can use BBcut's clock via the playExt method:

Pbind.new(/*  . . . */ ).playExt(clock);

Or, if you want to play an Event, you can use the tempo clock associated with the external clock

Event.new.play(clock.tempoclock);

Groups and FX

If we want to add fx to the chain, and take them back out, we can use a thing called a CutGroup:

  // make a group with a Buffer
  group = CutGroup(CutBuf3(sf1, 0.4));
  // then send it to get cut up
  cut1 = BBCut2(group, BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);
  // then put some FX in the chain
  group.add(CutMod1.new);

The CutGroup acts like an array, which holds our CutBuf and also the fx. To get an idea of how this works, try running the following code, adapted from CutGroup's help file:

(
var sf, clock;

clock= ExternalClock(TempoClock(2.5)); 
  
clock.play;  
  
Routine.run({
   
sf= BBCutBuffer("sounds/break",8);

s.sync; //this forces a wait for the Buffer to load

g=CutGroup(CutBuf1(sf));

BBCut2(g, WarpCutProc1.new).play(clock);
});

)

//run these one at a time
g.cutsynths.postln; //default CutMixer was added

g.add(CutComb1.new);

g.cutsynths.postln;

g.add(CutBRF1.new);

g.cutsynths.postln;

g.removeAt(2);  //remove comb

g.cutsynths.postln;

Note that the fx that you add on start with index 2.

And also notice that you may get some errors in the post window when you remove fx: FAILURE /n_set Node not found. These are not usually a problem.

Tying it all Together

Here's an example using everything covered so far:

(
 var clock, sf1, sf2, cut1, cut2, group;
 
 TempoClock.default.tempo_(180/60);
 clock = ExternalClock(TempoClock.default); 
 clock.play;
 
 sf1= BBCutBuffer("sounds/break",8);
 sf2= BBCutBuffer("sounds/break2",4);

 Routine.run({
  s.sync; // this tells the task to wait

  group = CutGroup(CutBuf3(sf1, 0.2));  // make a group with a Buffer
  cut1 = BBCut2(group, BBCutProc11(8, 4, 2, 2, 0.2)).play(clock);  // then cut it up
  
  5.wait;
  
  cut2 = BBCut2(CutBuf3(sf2, 0.2),
   BBCutProc11(8, 4, 4, 2, 0.2)).play(clock); // start more drums from the other sound file
  
  5.wait;
  
  group.add(CutComb1.new); // put some FX on the drums in cut1
  
  15.wait;
  
  group.removeAt(2); // take the fx back off
  
  1.wait;
  cut2.pause;
  
  4.wait;
  cut1.stop;
 })
)

Summary

  • You must download BBCut2 from a website and install it by moving folders around.
  • The main BBCut helpfile is BBCut2Wiki
  • BBCut uses it's own clock, called ExternalClock, which relies on TempoClocks.
  • You must remember to start the clock
  • BBCut uses it's own buffer class called BBCutBuffer. The constructor takes two arguments: a string with the path and filename, and the number of beats in the sound file. If you get the number of beats wrong, your results may sound weird.
  • There are several cut procedures, one of which is called BBCutProc11. To use it, (or any other cut procedure in it's place), you use the construction BBcut2(CutBuf1(sf), BBCutProc11.new).play(clock)
  • The cut procedures all have good help files.
  • Due to the magic of clocks, you can start two BBCuts going at the same time and if they have the same clock, they will line up.
  • It is possible to add FX or remove FX from your chain by using CutGroup.

Problems

  1. Get some of your own drum loops or rhythmic samples and try this out. You can find many loops at http://www.freesound.org/. Also, try some files with sustained tones.
  2. Experiment with the arguments to BBCutProc11. Try your files with several different values.
  3. Try out the different cut procedures. Look at their help files to find their arguments.
  4. The FX are not as well documented, but they're located in ~/Library/Application\ Support/SuperCollider/Extensions/bbcut2\ classes/cutsynths/fx . Try several of them out.

More Buffers

Waiting

Last time, we learned that the language does not wait for the server to finish loading buffers before it carries on with the programme, which could lead to a situation where we instruct the server to start playing a buffer that hasn't yet loaded. This will fail to play correctly. Fortunately, there are a few ways to make sure this doesn't happen.

If we use a Task (or a Routine), we can tell it to pause until the server has caught up.

s.boot;
 
(
 var buf;
 
 SynthDef(\playBufMono, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 buf = Buffer.read(s, "sounds/a11wlk01.wav");

 Task.new({
  
  s.sync; // wait for the server
  Synth(\playBufMono, [\out, 0, \bufnum, buf.bufnum, \rate, 1])
  
 }).play
)

The s.sync makes the Task wait for the server to get caught up with the language. The SynthDef, like the buffer, is also asynchronous, so the sync gives everything a chance to get caught up.

We can also give the buffer an action. This is a function that gets evaluated when the buffer has finished loading. This might be a good idea when we are going to load several buffers, but don't need to use all of them right away. We could use the action, for example, to set a flag so our programme knows it's ok to start using the buffer:

s.boot;
 
(
 var buf, bufloaded;
 
 SynthDef(\playBuf, {| out = 0, bufnum = 0, rate = 1, 
       dur = 0.2, amp = 0.2, startPos = 0 |
  var scaledRate, player, env;
  
  env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, 
       startPos: startPos, loop:1);
  Out.ar(out, player * env)
 }).add;

 bufloaded = false;
 
 buf = Buffer.read(s, "sounds/a11wlk01.wav", 
  action: { bufloaded = nil});


 Pseq([
  Pbind( // play this Pbind first
   \scale,  Scale.gong,
   \dur,   Pwhite(1, 3, inf) * 0.001,
   \degree,  Prand([0, 2, 4, \rest], inf),
   \amp,  0.2,
   \test,  Pfunc({ bufloaded }) // when this is nil, this Pbind ends
  ),
  Pbind( // next play this one
   \instrument, \playBuf,
   \bufnum,  Pfunc({buf.bufnum}), // use the buffer, now that we know it's ready
   \dur,  Pwhite(0.01, 0.3, 20),
   \startFrame, Pfunc({buf.numFrames.rand}),
   \amp,  1
  )
 ], 1). play

)


Recall that when a Pbind gets a nil, it stops playing and the pattern goes on to the next section, so setting the flag to nil, in this case, advances the piece. Because the second buffer has not yet loaded when the interpreter first evaluates the second Pbind, buf.numFrames will still be nil. Therefore, we put that into a Pfunc so that it gets evaluated when the Pbind plays, instead of when the interpretter first looks at the code.

In that example, we don't always start playing back at the start of the Buffer, but instead offset a random number of samples (called "Frames" here). Coupled with the short duration, this can be an interesting effect.

When you are writing your own actions, you can also do a more typical true / false flag or do anything else, as it's a function. For example, it's possible to combine the two approaches:

s.boot;
 
(
 var buf1, syn, pink;
 
 SynthDef(\playBuf1, {| out = 0, bufnum = 0, rate = 1, loop = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, loop: loop,
       doneAction:2);
  Out.ar(out, player)
 }).add;

 SynthDef(\playBufST, {| out = 0, bufnum = 0, rate = 1, loop = 1,
       dur = 1, amp = 0.2 |
  var scaledRate, player, env;
  
  env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(2, bufnum, scaledRate, loop:loop);
  Out.ar(out, player * env)
 }).add;

 buf1 = Buffer.read(s, "sounds/a11wlk01.wav");

 Task.new({
  
  s.sync; // wait for buf1
  syn = Synth(\playBuf1, [\out, 0, \bufnum, buf1.bufnum, 
       \rate, 1, \loop, 1]);  // play buf1

  pink =  Buffer.read(s, "sounds/SinedPink.aiff",
   action: {  // run this when pink loads
    syn.set("loop", 0);
    syn = Synth(\playBufST, [\out, 0, \bufnum, pink.bufnum,
        \rate, 1, \loop, 1, \dur, 10]); // play pink
   });
    
  
 }).play
)

In the above example, we first wait for the allwlk01 buffer to load and then start playing it. While it's playing, we tell the SinedPink buffer to load also and give it in action. When it has loaded, the action will be evaluated. The action tells the first synth to stop looping and then starts playing a new Synth with the new buffer.

If we wanted to, we could use the action to set a flag or do any number of other things.

Recording

We don't need to just play Buffers, we can also record to them. Let's start by allocating a new Buffer:

b = Buffer.alloc(s, s.sampleRate * 3, 1)

The first argument is the server. The second is the number of frames. I've allocated a 3 second long Buffer. The third argument is the number of channels.

Now, let's make a synth to record into it:

 SynthDef(\recBuf, {| in = 1, bufnum, loop = 1|
 
  var input;
  input = AudioIn.ar(in);
  RecordBuf.ar(input, bufnum, recLevel: 0.7, preLevel: 0.4, loop:loop, doneAction: 2);
 }).add;

AudioIn.ar reads from our microphone or line in. Channels start at 1, for this UGen.

RecordBuf takes an input array, a bufnum and several other arguments. In this example, we're scaling the input by 0.7 and keeping previous data in the buffer, scaled by 0.4. We'll keep looping until instructed to stop.

We can mix this with a stuttering playback from earlier:

s.boot;
 
(
 var buf, syn, pb;
 
 SynthDef(\playBuf2, {| out = 0, bufnum = 0, rate = 1, 
       dur = 5, amp = 1, startPos = 0 |
  var scaledRate, player, env;
  
  env = EnvGen.kr(Env.sine(dur, amp), doneAction: 2);
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, 
       startPos: startPos, loop:1);
  Out.ar(out, player * env)
 }).add;
 
 SynthDef(\recBuf, {| in = 1, bufnum, loop = 1|
 
  var input;
  input = AudioIn.ar(in);
  RecordBuf.ar(input, bufnum, recLevel: 0.7, preLevel: 0.4, loop:loop, doneAction: 2);
 }).add;
 
 buf = Buffer.alloc(s, s.sampleRate * 3, 1);
 
 Task({
  
  s.sync; // wait for the buffer
  
  syn = Synth(\recBuf, [\in, 1, \bufnum, buf.bufnum, \loop, 1]); // start recording
  
  2.wait; // let some stuff get recorded;
  
  
  pb = Pbind( // play from the same buffer
   \instrument, \playBuf2,
   \bufnum,  buf.bufnum,
   \dur,  Pwhite(0.01, 0.5, 2000),
   \startFrame, Pwhite(0, buf.numFrames.rand, inf),
   \amp,  1
  ).play;
  
  5.wait;
  
  syn.set("loop", 0);  // stop looping the recorder
  
  3.wait;
  pb.stop; // stop playing back
 }).play
)

Summary

  • Buffers load asynchronously and we can't count on them to be ready unless we wait for them.
  • One way to wait is to call s.ync inside a Task or a Routine.
  • We can start playing back a Buffer from any sample with the startFrame argument.
  • Buffer.read has an action argument, to which we can pass a function that will be evaluated after the Buffer has been read on the server.
  • We can allocate empty Buffers on the server with Buffer.alloc.
  • AudioIn.ar starts with channel 1, even though most other input busses start at 0.
  • RecordBuf.ar records to a Buffer.

Problems

  1. Write a programme that opens and plays several Buffers. Make sure that every Buffer is ready before you play it. Spend the least possible amount of time waiting.
  2. Write a programme that reads a Buffer, starts playing it back in some way and the starts recording to the same Buffer. You can use AudioIn.ar or check out InFeedback.ar. Note that, as the name implies, you may get feedback, especially if you are playing back in a linear manner at a rate of 1. Try other rates or ways of playing back to avoid this.
  3. Find a short audio file that contains some beat-driven audio. If you know the number of beats in the file, could you make an array of startFrames? Write a Pbind that does something interesting with your file and your array.

Sunday, October 03, 2010

Working with Buffers

Let's say you want to open a short audio file and play it back. You can do this with a Buffer.

 s.boot;
 b = Buffer.read(s, "sounds/a11wlk01.wav");

Don't run both of those lines at once. Wait for the server to finish booting before reading the Buffer. The reason you need to wit is because the Buffer actually lives with the server. The server plays the audio, so the server needs to have the audio in it's memory.

The first argument, is therefore the server that will hold the buffer. The second is the path to the buffer on your computer.

Because the Buffer lives with the server, the command to read it is asynchronous. We tell the server to load the Buffer and then carry on, without waiting to hear back if it's been loaded or not. For large files, it may take a few moments for it to fully load. One way to deal with tis is to load buffers into global variables (like b, above) and then manually wait to evaluate the next section.

PlayBuf

To play the Buffer, you can use the UGen PlayBuf:

(
 SynthDef(\playBuf, {| out = 0, bufnum = 0 |
  var scaledRate, player;
  scaledRate = BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum]);
)

When Buffers are allocated on the server, they're given a unique ID number, so we can reference them later. This number is called the bufnum. It's the second argument to the SynthDef. This way, we know which buffer we're supposed to play.

Some Buffers may have unusual sample rates or otherwise be unusual. To make sure that they play at the speed we expect, we use a UGen to scale the rate. If we wanted to play at half speed, we could change the SynthDef:

(
 SynthDef(\playBuf, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 0.5]);
)

Note that when we play at half speed, it takes twice as long to play the sample and pitches are all an octave lower. This is just like working with magnetic tape. If we change the rate to 2, it will play back at half the speed of the original and be an octave higher. If you try a rate of -1, it will play backwards at the normal rate.

The PlayBuf UGen is the one that actually plays the buffer. The first argument is the number of channels. Our buffer is mono, so that number is 1. If it were stereo, we would change it to 2. You cannot pass in this number as an argument or change it while the Synth is running. The SynthDef must be defined with the right number of channels. If you give a stereo buffer to a mono PlayBuf, or vice versa, it will not play. If you have both mono and stereo buffers in one piece, you will need to have two SynthDefs:

(
 SynthDef(\playBufMono, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 SynthDef(\playBufStereo, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;
)

PlayBuf can handle N-channel files, so if you have some three channel or 8 channel files, you can also play those if you define synthdefs with the right number of channels.

To figure out which SynthDef to use, you can send the message numChannels to a buffer object:

(
 if((b.numChannels == 1), {
  Synth(\playBufMono, [\out, 0, \bufnum, b.bufnum, \rate, 1])
 }, {
  if((b.numChannels == 2), {
   Synth(\playBufStereo, [\out, 0, \bufnum, b.bufnum, \rate, 1])
  })
 });
)

The second argument to PlayBuf is the bufnum. This tells it which Buffer to play.

The third argument is the rate. As mentioned above, it's a good idea to scale this rate with BufRateScale.kr

Buffers, like envelopes, can have a doneAction. 2 means to deallocate the synth from the server when the Buffer stops playing.

If you want to loop your buffer, that's also a possible argument:

(
 SynthDef(\playBufMonoLoop, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, loop: 1, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 1]);
)

1 means do loop and 0 means don't loop. the default is 0. You can change the loop value while the Synth is running, so it's a good idea to still have a doneAction.

You can trigger a PlayBuf to jump back to the start:

(
 SynthDef(\playBufMonoStutter, {| out = 0, bufnum = 0, rate = 1 |
  var trigs, scaledRate, player;
  trigs = Dust.ar(2);
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, trigs, loop: 1, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 1]);
)

Dust.ar returns impulses at random intervals. It's argument is the average number of impulses per second.

You can also tell the PlayBuf to start someplace else than the first sample:

(
 SynthDef(\playBufMonoLoopStart, {| out = 0, bufnum = 0, rate = 1, startPos |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, startPos: startPos, loop: 1, doneAction:2);
  Out.ar(out, player)
 }).play(s, [\out, 0, \bufnum, b.bufnum, \rate, 1, \startPos, b.numFrames.rand]);
)

startPos is the number of the sample to start from. If you have a one second long buffer with 44100 samples in it, and you want to start in the middle, you would set use 22050 for the startPos.

Dialogs

If you don't want to have to pick an audio file ahead of time, you can use Buffer.loadDialog:


s.boot;
 
(
 SynthDef(\playBufMono, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 SynthDef(\playBufStereo, {| out = 0, bufnum = 0, rate = 1 |
  var scaledRate, player;
  scaledRate = rate * BufRateScale.kr(bufnum);
  player = PlayBuf.ar(1, bufnum, scaledRate, doneAction:2);
  Out.ar(out, player)
 }).add;

 b = Buffer.loadDialog(s);
)

(
 if((b.numChannels == 1), {
  Synth(\playBufMono, [\out, 0, \bufnum, b.bufnum, \rate, 1])
 }, {
  if((b.numChannels == 2), {
   Synth(\playBufStereo, [\out, 0, \bufnum, b.bufnum, \rate, 1])
  })
 });
)

Note that you cannot load a compressed audio format like mp3 or ogg directly into a Buffer.

More on Buffers to come!

Summary

  • Buffers live on the server
  • Every Buffer has a unique bufnum
  • You can change the playback rate of a Buffer. This should be scaled with BufRateScale
  • SynthDefs must be sent to the server already knowing how many channels PlayBuf.ar will play.
  • PlayBuf.ar will optionally take a doneAction
  • You can loop a Buffer, stuter it and tell it where to start
  • You can open a Buffer with a Dialog box
  • You cannot read an mp3 or ogg directly into a Buffer

Tuesday, August 03, 2010

Event

The most important subclass of Dictionary is Event:

(
 var evt;
 evt = Event.new;
 evt.play;
)

Much like Arrays can be declared just by square brackets, Events can be declared just with parenthesees:

( ).play;

The sonic results of that line of code might sound familiar to you:

Pbind.new.play

This is not a coincidence! Patterns create Events and play them sequentially. Compare:

Pbind(\freq, 660, \pan, -1).play

and

(freq: 660, pan: -1).play

Note that the syntax for the Event is (key: value, key2: value2) . You can have as many key: value pairs as you want. In the example above, freq and pan are both symbols, both in the Pbind and the event.

Events are Dictionaries and they are created with a bunch of default keys and values. Although, you can use any sort of object as the key for a Dictionary, you should only use symbols as keys for an Event.

The Pbind modifies elements of a default Event and then plays the results of that. To find out what interesting thigns can be changed in an Event, (and therefore also in a Pbind), check out it's helpfile, specifically the section called "Useful keys for notes."

In your Pbind, you can get access to the current event:

(
 Pbind(
  \freq, Pseq([550], 1),
  \event, Pfunc({ |evt|
     evt[\freq].postln;
    })
 ).play
)

The current event is passed as an argument to the function in Pfunc and also the one in Prout. Here, we have created a key, \event, that doesn't have meaning to the Synth, nor to the Event. This is allowed. For example:

(
 Pbind(
  \freq, Pseq([550], 1),
  \foo, \bar
 ).play
)

Every time the Pbind makes an event, it goes through the list of keys and values in the order that it recieves them. For example:

(
 Pbind(
  \event, Pfunc({ |evt|
     "Pan before: %".format(evt[\pan]).postln;
    }),
    
  \pan, Pseq([-1], 1),
  
  \event2, Pfunc({ |evt|
     "Pan after: %".format(evt[\pan]).postln;
    })
 ).play
)

The first postln prints out 0, because that's the default value for \pan. The second postln prints out -1. Then it goes back to the first one because the Pbind does not run out the Pseq returns a nil.

Let's make the duration depend on the frequency:

(
 Pbind(
  \freq, Prand([440, 550, 660, 770], 10),
  \dur, Pfunc({ |evt|
   
     var dur;
     
     dur = evt[\freq] / 1000
    })
 ).play
)

Events are one way you can tie together two keys in an event. You can also do this with arrays:

(
 Pbind(
 
  [\freq, \dur],
   Prout({
    var arr, freq, dur;
    
    arr = [440, 550, 660, 770];
    
    10.do({
     
     freq = arr.choose;
     dur = freq / 1000;
     
     [freq, dur].yield;
    })
   })
 ).play
)

Two or more keys can be grouped together in an array. Then, the stream, which can be a Prout, a Pfunc or any other P-thing, must return an array which is the same length as the key array. The first item in the returned array gets paired with the first item in the key array, the second item goes with the second key and so forth.

Summary

  • Event is a subclass of Dictionary.
  • You can declare an event by just using parenthesis.
  • You should only use Symbols as keys for Events.
  • Patterns work by creating events over and over again.
  • You can access events in Pfunc. It's passed to the function as an argument.
  • You can use keys in your Event or Pbind that are not meaningful to the Pbind, the Even or the Synth.
  • Events can be used to tie together two different elements in a Pbind.
  • Arrays can also be used to tie together two different elements in a Pbind.

Friday, June 25, 2010

Dictionary

Let's take a break from Pbinds and their ilk to talk about a data type. A Dictionary is a data type like an array, except that instead of using integers for indexes, you can use anything that you want.

(
 var id;
 
 id = Dictionary.new;
 id.put(\foo, "bar");
 
 id[\foo].postln;
)

The indexes are called keys and the items associated with them are called values. In the above example, \foo is the key and "bar" is the value. As mentioned above, you can use any kind of object for keys. And, like arrays, any kind of object can be the value.

(
 var id;
 
 id = Dictionary.new;
 id.put(\foo, "bar");
 id.put(15, true);
 id.put("baz", 12.5);
 
 id[15].postln;
)

You can get a list of the keys with the message keys, and then use that to step through a do loop:

(
 var id, keys;
 
 id = Dictionary[
  \foo -> "bar",
  15 -> true,
  "baz" -> 12.5
 ];

 keys = id.keys;
 keys.postln;

 keys.do({|key|
  
  "key is %, value is %".format(key, id.at(key)).postln;
 });
)

In that example, we see another way of declaring a Dictionary, with square brackets and key, value pairs in the format key -> value

We also see a new way of formatting strings. If you want to print a string with some variables in it, you can use % to signify where the variable should go and then pass the variables as arguments to format. You can use as many variables as you would like: "string %, % and %".format(var1, var2, var3)

Notice that the keys are in a random order. If you want them in a particular order, you will have to sort them.

Here's another example:

(
 var triangle_spectrum, freqs;
 
 triangle_spectrum = Dictionary[
  1->1, 
  3->(1/9), 
  5->(1/25), 
  7->(1/49), 
  9->(1/81)
 ];
 
 freqs = triangle_spectrum.keys;
 freqs = freqs.asArray;
 freqs = freqs.sort;
 
 freqs.do({|freq|
 
  "freq %\t amplitude %\n".postf(freq * 440, triangle_spectrum[freq]);
 })
)

This stores the (non-normalised) spectrum of a triangle wave. Note that when we declare the Dictionary, we need to put mathematical expressions inside parentheses.

Then, we get the keys from the dictionary. Then we need to convert that Set to an Array. We can then tell the Array to sort itself.

\t means tab and \n means newline. We use these for formatting, to make our output look nice. postf formats a string and then posts it. It does not include a newline, so we do it ourselves with a \n

Array.sort takes an optional argument, which is a sort function. Here's an example to do a reverse sort:

(
 var triangle_spectrum, freqs;
 
 triangle_spectrum = Dictionary[
  1->1, 
  3->(1/9), 
  5->(1/25), 
  7->(1/49), 
  9->(1/81)
 ];
 
 freqs = triangle_spectrum.keys;
 freqs = freqs.asArray;
 freqs = freqs.sort({|a, b| a > b});
 
 freqs.do({|freq|
 
  "freq %\t amplitude %\n".postf(freq * 440, triangle_spectrum[freq]);
 })
)

The sort function takes two agruments and returns a boolean. In the above example, it compares two items and if the first one is bigger, it returns true. This will order the array so that larger items come first.

We can check to see if a Dictionary includes a particular key:

(
 var triangle_spectrum;
 
 triangle_spectrum = Dictionary[
  1->1, 
  3->(1/9), 
  5->(1/25), 
  7->(1/49), 
  9->(1/81)
 ];
 
 triangle_spectrum.includesKey(4).postln;
)

We can also look for the key of a particular item:

(
 var id;
 
 id = Dictionary[
  \foo -> "bar",
  15 -> true,
  "baz" -> 12.5
 ];

 id.findKeyForValue(12.5).postln;
)

Summary

  • A Dictionary is made up of key, value pairs.
  • You can add items to a Dictionary with a message put(key, value)
  • Dictionaries can be declared with key -> value pairs in square brackets.
  • Dictionary.keys exports a set of the keys, in random order.
  • If you want to sort the keys, you must pass them the message .asArray and then pass the message sort to the result of that.
  • If you want to sort anyhting besides numbers from small ot large you have to write a sort function
  • A sort function takes two arguments and returns a boolean based on a comparison between them.
  • You can test is an Dictionary contains a particular key with includesKey
  • You can look up the key for a value with findKeyForValue