Showing posts with label sc. Show all posts
Showing posts with label sc. Show all posts

Wednesday, October 13, 2010

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, May 23, 2010

Functions

After reading the last chapter on SynthDefs and the one before that on Variables, Nicole feels like she's got the hang of SuperCollider so she's dropped out of grad school to work for the new SuperCollider start-up company SuperSounds.com. On her first day, her boss tells her to write a function that prints "hello world" four times. "No problem," she thinks and goes to look at previous posts. Functions are code blocks encased by curly brackets, { } and hello world is easy enough. So she writes:

(
{

var greet;
greet = "hello world";

greet.postln;
greet.postln;
greet.postln;
greet.postln;
}
)

Then she thinks, "I should have asked for a signing bonus." She tries running it, by double clicking to the right of the top parenthesis and hitting enter. In the Untitled output window it says, "a Function"

What's the problem? She declared a variable called greet. greet gets "hello world". Then she sends a postln message to greet four times. Every line ends with a semicolon . . .

Then she realizes that she defined a function, but never told the interpreter to run it. The interpreter saw a code block surrounded by curly brackets and thought "a function!" Then it thought, "What do you want me to do with this? Nothing? Ok, I'll throw it away." So Nicole modifies her code:

(
var func;

func = {

var greet;
greet = "hello world";

greet.postln;
greet.postln;
greet.postln;
greet.postln;
};

func.value;
)

And then it works great. value is a message you can send to functions. It means, "Run yourself!"

But then Nicole gets a message from her boss saying, "Sometimes, we need to print out hello world five times and once in a while, three times, and rarely, it needs to print out infinite times." Nicole considers writing a few different versions of the function and calling them func4, func3, etc, but then remembers about arguments to functions.

(
var func;
func = { arg repeats;

var greet;
greet = "hello world";

repeats.do ({
greet.postln;
});
};

func.value(4);
)

When she writes her function, she declares to the interpreter that the function takes one argument. An argument is a special type of variable that gets set when the function is called. When she calls the function withfunc.value(4);, she's assigning a value to the argument repeats.

Then, inside the function, she's written, repeats.do. What is 'do'? It's a message. It takes a function as an argument. 'do' is a message you can send to integers that runs the function passed as an argument the number of times as the integer that it was called on. In this example, repeats is 4, so it runs four times.

What is an integer? An integer is a whole number. -2, -1, 0, 1, 2, 3, 4, etc. There is a special integer in SuperCollider called inf. It means infinity. If we try calling our above function, with

func.value(inf);

hello world will print out forever, or until we stop the program by hitting apple-. .

Then Nicole's boss sends another email saying that marketing has some changes. Every line needs to start out with the line number, starting with zero. So she makes another change:

(
var func;
func = { arg repeats;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value(4);
)

The function called by do takes an argument. And that argument is the number of times the loop has been run, starting with 0. So her output from this program is:

0 hello world
1 hello world
2 hello world
3 hello world

post just means print without a new line at the end.

Almost every time, she runs this function, the argument is going to be 4. So she can declare a default argument for the function.

(
var func;
func = { arg repeats = 4;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value;
)

When she calls func.value, if there's no argument, then the interpreter will assign 4 to repeats by default. If she calls it with, func.value(6); then repeats gets 6 instead. What if she accidentally passes in something besides an Integer into her function? That depends. If the object she passes in also understands a do message, then it will use that instead, although the result may differ. Otherwise, she will get an error.

What if the function took a lot of arguments with default values?

(
var func;
func = { arg foo = 0, bar = 0, baz = 1, repeats = 4;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value;
)

If she wants to pass in arguments, she does it in the order they're declared in.

func.value(0 /* foo */, 0 /* bar */, 1 /* baz */, 3 /* repeats */);

However, if we're happy with all the default values, there's a way to tell the function just to assign to a particular variable, out of order:

func.value(repeats: 3);

You can also just pass in the first N arguments and leave the remainder to the default values:

func.value(3, 1);

And you can combine approaches:

func.value(2, repeats: 6);

Those slash-stars up there are comments. The interpreter ignores everything between a forward slash star and a backslash star. They can span multiple lines. You can also create single line comment by using forward slash forward slash:

// This is a comment

Some times it's useful to comment out a line of code while debugging. So you can skip a particular line in order to figure out where your error is.

The philosophical point of a function is to return a value. Doing things within functions, like printing are technically called side effects. What the function returns is the value of its last line. Let's change that function so it returns the number of times that it printed.

(
 var func;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 
 func.value;
)

Now, if we create a new variable called times, we can assign the output of the function to it.

(
 var func, times;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 
 times = func.value;
 times.postln;
)

Prints out hello world with the line number like before, and then at the bottom, prints out a 4. Or we could change those last two lines from

 times = func.value;
 times.postln;

to

func.value.postln;

and the interpreter will read that statement left to right, first finding the value of the function and then sending that value a postln message.

Ok, what happens if we take the above function and write some code below it that looks like this:

(
 var func, times;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 greet.postln;
)

We get errors.

• ERROR: Variable 'greet' not defined.
 in file 'selected text'
 line 14 char 6 :
 greet•.postln;

This is because of something called scope. Variables only exist in the code block in which they are declared. Code blocks are zero or more lines of code surrounded by curly braces or highlighted by the user. This means that variables and arguments declared inside a function only exist inside that function. index does not exist outside of its function, which is the function passed as an argument to repeats.do. greet does not exist outside of it's function. None of these variables exist of the text we highlight.

Variables in outer blocks are accessible within inner blocks. For example,

(
 var func, times;
 times = 0;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  times.postln;
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
)

Is fine because times exists in the outer most block. In the same way, we can use greet inside our repeats.do function.

There are, however, a few variables that can be used everywhere. The interpreter gives us 26 global variables. Their scope is all of SuperCollider. They are single letter variable names, a, b, c, d, e, f, etc. You don't need to declare them and they keep their value until you change it again, even in different code blocks. This is why the variable 's' refers to the Server. You can change that, but you might not want to.

If you have any questions about functions, you can look at the Function help file for more information. The most useful message you can send to a function is value, but there are a few others, which you can read about. You may not understand everything you see in the helpfiles, but it’s good to keep reading them.

Summary

  • Blocks of code surrounded by curly brackets are functions.
  • Functions do not run unless you tell the interpreter to run them, which you can do with the message "value".
  • You can declare arguments to function. That argument can have a default value.
  • An integer is a whole number
  • If you pass the message "do" to an integer with a function as an argument, that function will run as a loop.
  • Functions can have many arguments. You must pass arguments in the correct order or list them by name.
  • Comments are either surrounded by /* and */ or are on a single line, preceded by //
  • All functions return a value, which is the result of the last line of code in the function
  • Variable names are only valid in the block of code in which they were declared, and within blocks of code contained within that block.
  • There are 26 global variables, each a single lowercase letter a-z.

Problems

  1. Write a function with nested do-loops, so that there is one loop inside the other.
  2. If x and y are intergers and the inner loop is x.do and the outer loop is y.do, how many total times with the inner loop be run?

Friday, December 11, 2009

Variables

In previous posts, like, Object Oriented Programming, we talked a bit about objects and variables, but in an abstract way. Now, let's get more specific.

A variable is a name for an object. For example, let's think of a person, Nicole, as an object. Now, Nicole, herself is an object, but the word "Nicole" is a name that refers to Nicole the person. Similarly, we can give names to our objects. These names are called variables. Let's see an example:

(
  var greeting; 
  greeting = "hello world";
  greeting.postln;
)

To run this code, copy it into a new window in Supercollider and then double click just inside the opening parenthesis to highlight all of it. Then, click the enter key. (Not the return key.) (see also: this video for help.) If you look in the Post window, it should say, "hello world."

What's going on there? The first word, "var," is short for variable. A variable is a storage location. It's a place to store a piece of data. The contents of data stored may vary, which is why it's called a variable. The second word "greeting" is a name. It is the name of the variables. Here we are declaring to the interpreter that we will have a variable named "greeting." The interpreter is the part of SuperCollider that reads and runs our programs. So, when the interpreter is reading our program and sees the word "greeting" it will know that greeting is a variable that we've declared. Otherwise, it wouldn't know what we were talking about.

All variable names in SuperCollider must start with a lowercase letter and cannot be a reserved word. You cannot have a variable called "var" because var already has a special meaning to the interpreter.

Next we are assigning a value to the variable. greeting gets "hello world". The variable is on the left of the equal sign. It must always be on the left. There can only ever be one thing on the left of an equals sign. That sign is saying, 'hey, take whatever is on the right of this equals sign and store it under the variable name on the left.'

In the last line, we're sending a postln message to the variable. The SuperCollider interpreter sends that message to greeting. greeting is a String. Strings print themselves with the postln message. So because greeting is a String, the contents of greeting, "hello world", print out.

On the left is the variable name, an object. Then is a period. Then is the name of the message. There are a few different coding styles allowable in SuperCollider, but we're going to focus on receiver notation because it is common across many programming languages. That is:

object.message;

Notice the semi-colons. In Supercollider, all instructions must be separated with a semi-colon. A single instruction can span many lines, so the interpretter needs the semi colon to know when one thing is done and another is starting. Later on, when you're trying to figure out why some program isn't working, you're going to discover a missing semicolon. To be on the safe side, it's good practice to put one at the end of every instruction, as in the example.

Summary

  • Variables are named bits of memory which can store objects.
  • Variables must be declared. Their names must start with lowercase letters
  • You can assign data to a variable by putting it alone on the left side of an equals sign.
  • We send messages to objects with the notation object.message
  • Lines of code must be separated with semicolons

The next chapter will make sounds.

Monday, April 07, 2008

Conductors: a fast GUI

I usually use Conductors because they're quick and dirty and can very easily be used to control a synth or a pbind. This class is from Ron Kuivila and it's included in the Wesleyan build or else available as quark. (See the Quarks helpfile for more on how to get it.)

Here's a quick example:

SynthDef("sin", {arg freq = 440;
  
  Out.ar(0, SinOsc.ar(freq))
}).store;
  
  
Conductor.make({arg this_conductor, freq;
  
  freq.spec_(\freq);
  
  this_conductor.synth_(
    ( instrument: "sin" ),
    [freq: freq]
  )
}).show
  

The constructor method for a conductor is make, which takes a function for an argument. The first argument to the function refers to the freshly-created conductor. Then, for the rest of the arguments, list things you want to control. So I put here "freq". freq is initalised as something called a CV.

CVs are very handy for scaling things. They have a low value, a high value, an initial value and can be linear or exponential. So a fader with a CV can slide back and forth and give you meaningful numbers in the range that you want. There's a bunch of pre-defined CV settings that can be useful for certain applications. Like here, the freq CV is being set to a pre-defined specification for audible frequencies.

Next, we're telling the conductor that it should be controlling a synth.

The first argument here is a list. We tell it which synth to use. We could also say which server to send it to and it's group and things like that.

The second argument is an array of name: value pairs. [name_of_synth_arg: name_of_cv, name_of_another_synth_arg: name_of_another_CV] This usage here ties the CV freq to the synth argument freq.

Finally we show the whole thing and you get a box with some buttons on it and a slider. The syntax of of the Conductor is slightly obtuse, but, as you can see from the box that popped up, you get a lot of stuff for free. If you click on the > button, it will start playing. Wiggle the fader. [] stops it.

We can modify the above example a little bit to see this maybe a bit clearer:

SynthDef("sin2", {arg freq1 = 440, freq2 = 10;
  
  Out.ar(0, SinOsc.ar(freq1, SinOsc.ar(freq2)))
}).store;
  
  
Conductor.make({arg this_conductor, freq, pm;
  
  freq.spec_(\freq);
  pm.spec_(\widefreq);
  
  this_conductor.synth_(
    ( instrument: "sin2" ),
    [freq1: freq, freq2: pm]
  )
}).show;

Ok, so the newer synth does some silly phase modulation.

The conductor now has two CVs called freq and pm. We set pm to have the pre-definied spec for frequencies that include the sub-audible range. Other such specs include: unipolar, bipolar, freq, lofreq, midfreq, widefreq, phase, rq, audiobus, controlbus, midi, midinote, midivelocity, db, amp, boostcut, pan, detune, rate, beats, delay If you want to see what those other ones are like, you can create a bunch of Conductors with them and wiggle the sliders around and see what you get for values.

And finally, if you look at the array to attach the synth, it's a bit clearer that freq1 belongs to the synth and freq comes from the conductor. freq2 belongs to the synth and pm comes from the conductor. And when you show it, you get two sliders.

Using a Conductor with a pattern is really straight forward:

Conductor.make({arg this_conductor, freq, amp;
  
  freq.spec_(\freq);
  amp.spec_(\amp);
  
  this_conductor.pattern_(Pbind(\freq, freq, \amp, amp)); }).show

When you play it, you'll have to turn up the amp. And you can put anything you want in that Pbind. It's just like any other Pbind. Anything you want on a gui, you can put in the argument for the Conductor. You can also do things like:

Conductor.make({arg this_conductor, freq, amp;
  
  freq.sp(3, 0, 5, 1, 'linear');
  amp.spec_(\amp);
  
  this_conductor.pattern_(
  Pbind(
    \amp, amp,
    \freq, Pfunc({[100, 250, 374, 580, 687, 910].at(freq.value)})
  ));
}).show

Here, the freq can be integers between 0 and 5. In the Pbind, a Pfunc uses the value of the freq slider as the index for an array. You need to do a whatver_cv.value to get the value that the slider is giving you. You can also set the value with whatever_cv.value_, which can make your sliders wiggle themselves around. If you want to wiggle them with a joystick or something, you might use whatever_cv.input_ and pass in a value between 0-1. This helps with the scaling.

So, for example, here's a change to the amp:

Conductor.make({arg this_conductor, freq, amp;
  
  freq.sp(3, 0, 5, 1, 'linear');
  amp.spec_(\amp);
  
  amp.value_(0.2);   
  this_conductor.pattern_(
    Pbind(
      \amp, amp,
      \freq, Pfunc({[100, 250, 374, 580, 687, 910].at(freq.value)})
  ));
}).show

Update

Conductors have been moved to become a Quark. This is a plugin system for SuperCollider that allows you to install extra libraries. If you are using the latest version of SuperCollider (any version of 3.7 alpha or higher or any version of 3.6 downloaded in the last several months), simply type: Quarks.gui and evaluate it to get a nice gui for all the quarks. Click the 'update quarks listing' button, then find Conductor in the list and mark it for install and click apply. Restart the interpreter (command-shift-l) before using any newly-installed Quark. If this does not work and you are on 3.6 or lower, see these instructions. For more information about Quarks in general, see the help page.