Showing posts tagged with: Tutorial Show all posts
Posted Tuesday, 14 May 2013
Git vs. Github?
I was trying to tell a friend the other day how Git and Github are related. Where does Git end and Github start? What do they each even do for you?
I realised that it's actually one of those fundamental things that is worth stepping through and getting a clear understanding. If you've been using the terms interchangeably, the distinction makes a good backdrop to learn more, and getting clarity will enable you to steer past a whole bunch of confusion later on.
What is Git?
Well, Git is not Github. Git is a piece of software that you install locally on your computer which handles 'version control' for you.
![]() |
So to learn about Git, you have to learn about version control.
What is Version Control?
Let's say you have some new project, and you are planning to store all the files for that project in some new directory. You know that as time goes on, the files in this project will change - a lot. Things could get messy, and who knows when you might need to revert back to a previous working version of what you had?
So, you install Git on your computer. Then, you have Git create the new project directory for you. You also tell Git that you would like to keep a history of the changes you make within that directory.
Then, you add some files to kick off your project. The files you just added represent the first incremental step on the journey of your project. So you tell Git to take a snapshot.
Then you make a small change - your next incremental step. So you take another snapshot.
And that's about it for version control - make a small change, take a snapshot, make another small change, take a snapshot. You can then use Git to step back and forth whenever necessary through each snapshot (snapshot aka version) of your project directory. Hence, version control.
And Git is just one of the many version control systems out there, that you can download and install on your machine. Hence, Git.
Collaborating with Git
That's great for you as an individual. But what if you are working on a team, and you want to share your project directory? And you want to make changes on your machine, send those changes to your collaborators, and also have changes they make appear in your machine's project directory?
Git is a so called distributed version control system. All that means is that Git has commands that allow you to push and pull your changes to other people's machines:
![]() |
Neither copy of the project directory is any better or 'greater' than any other - you are both collaborating on identical copies. This is a good thing, and Git gives you the power to work on your own copy as-is until you are ready to pull in your collaborator's changes, and push back your own changes.
But unless you are working right next to each other every day, you can't be sure exactly when your collaborator will have their machine on and plugged into a network. Wouldn't it be great if there were a third identical copy you could both push and pull from?
Collaborating with Git and GitHub
Well, that's what Github is! At it's core, it's just a place to store your identical working directories - aka repositories, or repo's for short. That's the service that Github provides - it's literally a hub for Git repositories.
![]() |
Github gives you a bunch more features, like a nice website to allow you to compare changes and administrate user accounts. But it's raison d'être is to host your repos, and to make it easier for you to push and pull from your collaborators.
Since Git and Github aren't really linked - Github is just another place to store identical repos - you could use any Git hosting service. One alternative is Bitbucket. This service gives you free private repos (unlike Github), in case you aren't ready to share your work with the world.
However Github is the most widely used Git hosting service, and has a broad community of users sharing code and interacting.
How to Learn Git
So in any case, the real challenge when you are starting out isn't learning Github, which is just an interchangeable service which allows you to host the thing of real value - your Git repository. Your attention is better spent learning Git.
The best way to learn Git in my opinion is this free online book: git-scm.com/book. It walks you through step by step and assumes no particular knowledge. There is an online, PDF and mobi version available, and it uses Github for hosting when you get to that stage.
There are a lot of topics to cover but for most users interacting on a fairly small scale, the first two chapters should suffice. You can pick up the harder stuff as and when necessary.
Another good place to go if you want to try out a few commands without going through the hassle of installing Git, is Try Git. Expect some commercial ad tie-ins, and it won't answer your questions like the book does. But it does let you give things a try and learn by doing.
Good luck!
Posted Monday, 4 June 2012
QuickRef for SuperCollider
In a previous post I mentioned Scott Wilson's tutorial as a really good starting point for learning SuperCollider (SC). Below I have summarised just about everything (including codeblocks) written in that tutorial, but this time all on one page!
This is so that once you have read the tutorial you have a Quick Reference to help you get started. You can navigate the material quickly using jumplinks:
- Tutorial #1: First Steps
- Tutorial #2: Start Your Engines
- Tutorial #3: Functions and Other Functionality
- Tutorial #4: Functions and Sound
- Tutorial #5: Stereo
- Tutorial #6: Mixing
- Tutorial #7: Scoping and Plotting
- Tutorial #8: Help
- Tutorial #9: SynthDefs
- Tutorial #10: Busses
- Tutorial #11: Groups
- Tutorial #12: Buffers
- Tutorial #13: Scheduling Events
- Tutorial #14: Scheduling with Routines and Tasks
- Tutorial #15: Scheduling with Patterns
...or just go ahead and scroll down the whole page.
There are also some common keyboard shortcuts and common SC methods right at the bottom: Common methods and shortcuts
[back to top]Tutorial #1: First Steps
Literals and no-args
You can treat literals as objects, and you can omit brackets for no-argument methods:
"Hello World!".postln;
8.rand;
Blocks (and variables)
To execute several statements at once, wrap them in a block - double-click anywhere inside the brackets to select the whole block:
(
"Call me, ".post;
"Ishmael.".postln;
)
[back to top]Tutorial #2: Start Your Engines
Boot a server
Boot a localhost server via the GUI, or by code:
s.quit;
s.boot;
Interpreter variabless is just an interpreter variable, which is pre-assigned the value from Server.local:
Server.local.boot;
Interpreter variables (a - z) are pre-declared when you start up SC, and have global scope.
Environment variables
You can make your own variables that act like interpreter variables - you don't have to declare them and they have global scope (but for the same reason they are not efficient):
~sources = //whatever
(They are introduced on the Groups page)
Local vs. internal
The local server is the localhost, communicated with over the network, but residing on the same machine as the client. Using the local server is no different to using any networked sever.
The internal server runs as a process within the client app; basically a program within a program. The main advantage is that it allows the two applications to share memory, which allows for things like realtime scoping of audio. The disadvantage is that the two are then interdependent, so if the client crashes, so does the server.
[back to top]Tutorial #3: Functions and Other Functionality
Functions are objects
Functions can be assigned and treated as objects:
f = { "Function evaluated".postln; };
f.value;
(The method .value just says 'evaluate this method now')
Arguments
Functions can have arguments:
f = { arg a, b; a - b; };
f.value(5, 3);
Equivalent syntax
Declare arguments within pipes:
f = { |a, b| a - b; };
Polymorphism
SuperCollider supports polymorphism:
f = { arg a; a.value + 3 };
f.value(3); // Send an int
f.value({ 3.0.rand; }); //Send a function
//...either way .value works just fine
Named parameters
You can use named parameters:
f = { arg a, b; a / b; };
f.value(b: 2, a: 10);
(You can mix regular and named parameters, but regular must come first)
Default arguments
You can set defaults:
f = { arg a, b = 2; a + b; };
f.value(2); // 2 + 2
Method variables
Internal method variables can be declared, but must be directly after args:
f = {
arg a, b;
var myVar1, myVar2;
//rest of method
};
Block variables
Internal block variables must be declared at the start of the block:
(
var myFunc;
myFunc = { |input| input.postln; };
myFunc.value("foo");
myFunc.value("bar"); //etc
)
Operator precedence
There isn't any. Use brackets:
1 + 2 * 3; //9
1 + (2 * 3); //7
[back to top]Tutorial #4: Functions and Sound
Instantiation methods
Various static methods are designed to help you instantiate objects with certain properties:
x = SomeObject.new(0.5) //equiv to x = SomeObject(0.5)
x = SinOsc.ar(440, 0, 0.2) //audio rate
x = SinOsc.kr(440, 0, 0.2) //control rate
Unit generatorsSinOsc is a type of UGen. The ar and kr methods accept common arguments:
SinOsc.ar(freq, phase, mul)
//frequency is in Hertz
//phase is in radians (between 0 and 2*pi)
//mul is multiplier (aka amplitude)
(Read more on phase)
The 'add' argumentUGens often also come with an optional 'add', as well as mul. These are useful to create kr signals, i.e. if you want to generate a smooth volume envelope:
SinOsc.kr(0.5, 1.5pi, 0.5, 0.5);
//frequency = once every 2 seconds
//phase = start with volume at 0
//mul = reduce from (-1 to 1) to (-.5 to .5)
//add = shift from (-.5 to .5) to (0 to 1)
Use the new kr UGen to control an ar UGen
Use the UGen as a volume envelope on a SinOsc:
{ var ampOsc;
ampOsc = SinOsc.kr(0.5, 1.5pi, 0.5, 0.5);
SinOsc.ar(440, 0, ampOsc);
}.play;
[back to top]Tutorial #5: Stereo
Arrays = multi-channel
Arrays are used to implement multi-channel audio. If your function returns an array of UGens, the .play method will assign each to available channels:
{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;
Multi-channel expansion
If you pass an Array argument to a UGen, you get an Array of that UGen:
{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;
{ SinOsc.ar([440, 442], 0, 0.2) }.play; //equivalent to above
Panning
Use the UGen 'Pan2' (demoed here with PinkNoise):
{ Pan2.ar(PinkNoise.ar(0.2), -0.3) }.play; //fixed at -0.3
{ Pan2.ar(PinkNoise.ar(0.2), SinOsc.kr(0.5)) }.play; //moving
(More on SuperCollider Collections)
[back to top]Tutorial #6: Mixing
Addition = mixing
Use +, and a low number for mul, to mix UGen outputs together:
{ PinkNoise.ar(0.2) + Saw.ar(660, 0.2) }.play;
The 'Mix' class
You can use Mix to mix an array of UGens into a single channel:
{ Mix.new(
[SinOsc.ar(440, 0, 0.2), Saw.ar(660, 0.2)]
).postln }.play;
...or to mix an array of arrays, i.e. an array of stereo channels, into a single stereo channel:
{
var a, b;
a = [SinOsc.ar(440, 0, 0.2), Saw.ar(662, 0.2)];
b = [SinOsc.ar(442, 0, 0.2), Saw.ar(660, 0.2)];
Mix([a, b]).postln;
}.play;
Note that Mix() is equivalent to Mix.new().
Mixing on a loopMix.fill allows you to mix the same UGen multiple times with parameters:
(
var n = 8;
{
Mix.fill(n,
{ SinOsc.ar(500 + 500.0.rand, 0, 1 / n) }
)
}.play;
)
Mixing on a loop with an index parameter
As above, but taking advantage of the index argument, which increments with each call:
(
var n = 8;
{
Mix.fill(n,
{
arg index;
var freq;
index.postln;
freq = 440 + index;
freq.postln;
SinOsc.ar(freq , 0, 1 / n);
}
)
}.play;
)
The index parameter is a special argument which is filled in for you by convention. See the list at the bottom of this post.
[back to top]Tutorial #7: Scoping and Plotting
Plotting
Make a graph of the signal produced by the output of the Function:
{ PinkNoise.ar(0.2) + Saw.ar(660, 0.2) }.plot; //default 0.01
{ PinkNoise.ar(0.2) + Saw.ar(660, 0.2) }.plot(1); // 1 sec
Scoping
(Internal server only - make sure it is booted)
Show an oscilloscope of the signal produced by the output of the Function:
{ PinkNoise.ar(0.2) + Saw.ar(660, 0.2) }.scope;
Scoping with zoom
Just add the named parameter:
{ PinkNoise.ar(0.2) + Saw.ar(660, 0.2) }.scope(zoom: 10);
Scoping any time
Scope the internal server anytime:
{
[SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)]
}.play(Server.internal);
Server.internal.scope; //or s.scope if internal is default
Frequency scope
A nice one (not mentioned in the tutorial) is the frequency analyzer:
{ PinkNoise.ar(0.2) + Saw.ar(660, 0.2) }.freqscope;
[back to top]Tutorial #8: Help
Syntax shortcuts
Common syntax shortcuts are listed here. There are a range of shortcuts, particularly good for dealing with collections.
Finding help
The tutorial help page is here, an extended help reference is here: More on Getting Help
Snooping around SC
SuperCollider has class browsers and other built-in approaches to snooping on source code - find out about them here.
Programmatically navigating the API
Objects have methods for finding definitions (this is introduced on the Groups page).
Group.superclass; //this will return 'Node'
Group.superclass.openHelpFile;
Group.findRespondingMethodFor('set'); //Node-set
Group.findRespondingMethodFor('postln'); //Object-postln
Group.helpFileForMethod('postln'); //opens Object help file
[back to top]Tutorial #9: SynthDefs
Functions create SynthDefs
If you play a function, behind the scenes it creates a SynthDef:
//these two are equivalent
{ SinOsc.ar(440, 0, 0.2) }.play;
SynthDef.new("tutorial-SinOsc",
{ Out.ar(0, SinOsc.ar(440, 0, 0.2)) }
).play;
(The first argument to SynthDef.new identifies the SynthDef, the second is a function known as a 'UGen Graph Function', since it tells the synth how to connect various UGens together to make a snyth)
Manipulate a SynthDefSynthDef.new returns a Synth, which you can manipulate / free:
x = { SinOsc.ar(660, 0, 0.2) }.play;
y = SynthDef.new("myDef",
{ Out.ar(0, SinOsc.ar(440, 0, 0.2)) }).play;
x.free; // free just x
y.free; // free just y
Send and load
Methods for sending a SynthDef to a server without causing playback. Allow you to create additional synths directly on the server without having to parse the code again:
SynthDef.new("myDef",
{ Out.ar(0, PinkNoise.ar(0.3)) }).send(s);
x = Synth.new("myDef");
y = Synth.new("myDef");
(send just sends, load saves to disk and the server reads from disk)
Objects evaluate only on the client
Compare the two examples - the first creates a new frequency each time, the second creates a new frequency once and that frequency is fixed with all new instantiations:
f = { SinOsc.ar(440 + 200.rand, 0, 0.2) };
x = f.play;
y = f.play;
z = f.play;
x.free; y.free; z.free;
SynthDef("myDef",
{ Out.ar(0, SinOsc.ar(440 + 200.rand, 0, 0.2)) }).send(s);
x = Synth("myDef");
y = Synth("myDef");
z = Synth("myDef");
x.free; y.free; z.free;
Use arguments to create variety
You could use the 'Rand' UGen to get round this, but more common to use args:
SynthDef("myDef", { arg freq = 440, out = 0;
Out.ar(out, SinOsc.ar(freq, 0, 0.2));
}).send(s);
x = Synth("myDef");
y = Synth("myDef", ["freq", 660]);
z = Synth("myDef", ["freq", 880, "out", 1]);
Change values after instantiation
Synth understands some methods which allow you to change the values of args after a synth has been created, one example is set:
SynthDef.new("myDef", { arg freq = 440, out = 0;
Out.ar(out, SinOsc.ar(freq, 0, 0.2));
}).send(s);
x = Synth.new("myDef");
x.set("freq", 660);
x.set("freq", 880, "out", 1);
Use symbols not strings
Symbols are more 'typesafe':
"a String" === "a String"; //false
\aSymbol === 'aSymbol'; //true
"this" === \this; //false
[back to top]Tutorial #10: Busses
Bus indices
Busses are zero-indexed, in the order: out, in, private:
2 outs, 2 ins 4 outs, 2 ins
---------------------------------
0: Output 1 0: Output 1
1: Output 2 1: Output 2
2: Input 1 2: Output 3
3: Input 2 3: Output 4
4: Private 1 4: Input 1
5: Private 2 5: Input 2
etc. 6: Private 1
7: Private 2
etc.
(See ServerOptions for information on how to set the number of input and output channels, and busses)
Read and write to bus indices
Use bus indices directly (first arg is 'base' index, second argument is number of channels, counting up from that):
Out.ar(0, SinOsc.ar(440, 0, 1));
In.ar(2, 1); //returns an OutputProxy
In.ar(2, 4); //returns an Array of 4 x OutputProxy
Use Bus objects to avoid index-handling
Get a two channel control Bus, and a one channel private audio Bus (one is the default):
b = Bus.control(s, 2);
c = Bus.audio(s);
Free up busses after use
The indices from Bus can then be reallocated:
b = Bus.control(s, 2);
b.free;
Busses support downsampling but not upsampling
The first is legal but the second is not:
{Out.kr(0, SinOsc.ar)}.play;
{Out.ar(0, SinOsc.kr)}.play;
(However most UGens support upsampling and some interpolate to create smooth waves)
Multiple synths on the same bus are summed
In other words, mixed:
SynthDef("myDef", { arg freq = 440, out = 0;
Out.ar(out, SinOsc.ar(freq, 0, 0.2));
}).send(s);
// both write to bus 1, and their output is mixed
x = Synth("myDef", ["out", 1, "freq", 660]);
y = Synth("myDef", ["out", 1, "freq", 770]);
Set the (constant) value of a kr bus
Use .set to send a fixed value:
b = Bus.control(s, 1); b.set(880);
c = Bus.control(s, 1); c.set(884);
Get the value of a bus (delegate).get takes a delegate function as an argument, because of the latency involved in requesting the sample from the server. The interpreter will go ahead and execute the next line of the main thread:
f = nil;
b = Bus.control(s, 1); b.set(880);
b.get({ arg val; val.postln; f = val; });
f.postln; //this will most likely be nil as it will be //executed before .get has had a response from //the server and executed the delegate
Map a bus to a Synth argument
Continuously evaluate a busses value in a Synth:
b = Bus.control(s, 1); b.set(880);
c = Bus.control(s, 1); c.set(884);
x = SynthDef("myDef", { arg freq1 = 440, freq2 = 440;
Out.ar(0, SinOsc.ar([freq1, freq2], 0, 0.1));
}).play(s);
x.map(\freq1, b, \freq2, c);
Order of execution
Within each cycle, the server runs through all synths in order. You have to maintain a good order so that synths that write samples to a bus are calculated before the synths that read the samples from that bus:
//.after and .before
x = Synth.new("xDef", [\bus, b]);
y = Synth.before(x, "yDef", [\bus, b]);
z = Synth.after(x, "zDef", [\bus, b]);
//equivalent to
x = Synth.new("xDef", [\bus, b]);
y = Synth.new("yDef", [\bus, b], x, \addBefore);
z = Synth.new("zDef", [\bus, b], x, \addAfter);
Above, x is the target, [\addBefore, \addAfter, \attToHead, \addToTail, \addReplace] is the addAction.
There are also methods: [Synth.head, Synth.tail, Synth.replace].
See more on Order of Execution, including info on groups.
Useful examples using busses
There are some really useful example code listings using busses - see Busses in Action and More Fun with Control Busses.
[back to top]Tutorial #11: Groups
Group and Synth both derive from Node
A Synth and Group are both types of Node:
Node
/\
Synth Group
Groups for bulk orderingGroups are useful for controlling order:
~sources = Group.new;
~effects = Group.after(~sources); //whole ~effects group after
x = Synth(\anEffect, ~effects);
y = Synth(\aSource, ~sources);
z = Synth(\aSource, ~sources);
(See an example with actual SynthDefs)
Groups for bulk messagingGroups allow you to easily group together Nodes and send them messages all at once:
g = Group.new;
4.do({ { arg amp = 0.1; PinkNoise.ar(0.2); }.play(g); });
g.set(\amp, 0.005); // turn them all down
Groups can contains Groups and Synths
Each Group can contain a mixture of Nodes, i.e. a combination of Synths and more Groups, i.e:
-Group
-Synth
-Synth
-Group
-Synth
-Synth
-Synth
Query all nodes
Find out what's happening on the server using s.queryAllNodes:
nodes on localhost:
a Server
Group(0) //RootNode
Group(1) //DefaultNode
Group(1000) //User-defined Groups and Synths
Group(1001)
Synth 1002
Root node and Default node
- RootNode is a top-level for everything to hang off
- DefaultNode is where all your new nodes should go
It's so that methods such as Server.scope and Server.record (which create nodes which must come after everything else) can function without running into order of execution problems.
[back to top]Tutorial #12: Buffers
Arrays on the server
Buffers are just in-memory arrays of floats on the server:
[0.1, 0.2, 0.3, 0.4, 0.5] [0.5, 0.4, 0.3, 0.2, 0.1]
(They are designed to handle sound but could really be any values)
Number of buffers available
Like busses, the number of buffers is set before you boot a server (using ServerOptions).
Buffers are zero-indexed
Buffers are zero-indexed, but as with Busses there is a handy Buffer class.
Allocate a 2-channel buffer
Use the Buffer class:
s.boot;
b = Buffer.alloc(s, 100, 2); // 2 channels, 100 frames
b.free; // free the memory when done
(Actual number of values stored is numChannels * numFrames, in this case there will be 200 floats)
Allocate in seconds rather than frames
Multiply by the server's sample rate:
b = Buffer.alloc(s, s.sampleRate * 8.0, 2); // 8 second stereo
b.free;
Read a file into a buffer
Read a file into a buffer:
b = Buffer.read(s, "sounds/any.wav");
Record sound into a buffer
Record sound into a buffer:
x = PinkNoise.ar(0.3);
RecordBuf.ar(x, b);
Playback from a buffer
Playback from a buffer:
x = SynthDef("myDef",{ arg out = 0, buf;
Out.ar( out,
//Args: numChannels, buffer, playbackSpeed
PlayBuf.ar(1, buf, BufRateScale.kr(buf))
)
}).play(s,[\buf, b]);
(BufRateScale scales the speed, in case the wavefile has a different sample rate to the server)
Play a file straight off the disk
Load it outside the synth so it can be reused. Note - no rate control:
SynthDef("myDef",{ arg out=0, buf;
Out.ar(out,
DiskIn.ar( 1, buf )
)
}).send(s);
b = Buffer.cueSoundFile(s,"sounds/mySound.aiff", 0, 1);
y = Synth.new("myDef", [\buf,b], s);
Get info from a buffer
Use it's properties:
b = Buffer.read(s, "sounds/mySound.wav");
b.bufnum;
b.numFrames;
b.numChannels;
b.sampleRate;
b.free;
...but make sure the file is loaded into the buffer first:
b = Buffer.read(s, "sounds/mySound.wav", action: { arg buffer; //read properties here });
Get and set data values
Remember multichannel buffers interleave their data, so for a two channel buffer index 0 = frame1-chan1, index 1 = frame1-chan2, index 2 = frame2-chan1, and so on:
b = Buffer.alloc(s, 8, 1);
b.set(7, 0.5); //index, value
b.get(7, {|msg| msg.postln});
Read / write multiple values
Note the upper limit on the number of values you can get or set is usually 1633, due to packet size:
b.setn(0, Array.fill(b.numFrames, {1.0.rand}));
b.getn(0, b.numFrames, {|msg| msg.postln});
Read / write > 1633 values
Use b.loadCollection and b.loadToFloatArray:
(
v = FloatArray.fill(44100, {1.0.rand2}); //white noise
b = Buffer.alloc(s, 44100);
)
(
// load the FloatArray into b, then play it
b.loadCollection(v, action: {|buf|
x = {
PlayBuf.ar(buf.numChannels, buf, BufRateScale.kr(buf),
loop: 1) * 0.2;
}.play;
});
)
x.free;
// Get the FloatArray back, compare it to v
// 0 = from beginning, -1 = load whole buffer
b.loadToFloatArray(0, -1, {|floatArray|
(floatArray == v).postln });
b.free;
Plot and play
Note play's argument - loop. If false (default) the resulting synth is freed automatically:
//see the waveform
b.plot;
//play the contents
b.play; //frees itself
x = b.play(true); //loops so doesn't free
Buffers for waveshaping
The method 'cheby' fills the buffer with a series of chebyshev polynomials. The 'Shaper' UGen then uses the dataset stored in the buffer to shape a SinOsc:
b = Buffer.alloc(s, 512, 1);
b.cheby([1,0,1,1,0,1]);
x = play({
Shaper.ar(
b,
SinOsc.ar(300, 0, Line.kr(0,1,6)),
0.5
)
});
[back to top]Tutorial #13: Scheduling Events
Three types of clock
Note that while there is only one SystemClock, there can be many TempoClocks all running at different speeds, if need be.
TempoClock- Musical sequencing, can change tempo and is aware of meter changesSystemClock- Actual time, in secondsAppClock- Also runs in seconds but has a lower system priority (better for graphic updates and other non-timecritical activities)
Schedule relative to current time
Say 'hello' in 5 seconds:
SystemClock.sched(5, { "hello".postln });
Schedule for an absolute time
Provide a time at which to say 'hello':
var timeNow = TempoClock.default.beats;
TempoClock.default.schedAbs(timeNow + 5,
{ "hello".post; nil });
Change the tempo
Change the tempo:
TempoClock.default.tempo = 2; // 2 beats/sec, or 120 BPM
Get a handle to the clock from inside a function
Use thisThread.clock. Once you know the clock, you can find out what time it is using beats:
SystemClock.beats;
TempoClock.default.beats;
AppClock.beats;
thisThread.clock.beats;
Fire a function many times
If you schedule a function that returns a number, the clock will treat that number as the amount of time before running the function again:
TempoClock.default.sched(1, { rrand(1, 3).postln; });
(Stop it with Cmd - .)
Fire a function once
Return Nil:
TempoClock.default.sched(1, { rrand(1, 3).postln; nil });
Don't return zero
You'll get an infinite loop.
[back to top]Tutorial #14: Scheduling with Routines and Tasks
(Note that the examples given here can be more elegantly expressed using Patterns, see Tutorial #15)
(See the note on timing and latency at the bottom if you use this approach, or read more about Server Timing)
Schedule a Routine
Get a full intro to Routines here.
r = Routine({
var delta;
loop {
delta = rrand(1, 3) * 0.5;
Synth(\someSynth);
delta.yield; //yield and return this value
}
});
Scheduled:
r.next;
TempoClock.default.sched(0, r);
r.stop;
Played immediately:
r.play;
r.stop;
Schedule a TaskTask is the same as Routine, except when you stop and restart a Task, it remembers where it last yielded and picks up from there.
t = Task({
loop {
[60, 62, 64, 65, 67, 69, 71, 72].do({ |midi|
Synth(\someSynth, [freq: midi.midicps]);
0.125.wait;
});
}
}).play;
t.stop; // probably stops in the middle of the scale
t.play; // should pick up with the next note
Synchronise Tasks.play takes several arguments to control its behavior:
aRoutine.play(clock, quant)
aTask.play(argClock, doReset, quant)
- doReset (Task only): If true, reset the sequence to the beginning before playing; if false (default), resume
- quant (both): - easier just to demonstrate:
aTask.play(quant: 4); //start on next 4-beat boundary
aTask.play(quant: [4, 0.5]); //next 4-beat boundary + half-beat
Use multiple datasets in a Task (with Routines)
By splitting out data declaration from looping. Here using Routines to store the datasets:
//datasets
var midi, dur;
midi = Routine({
[60, 72, 71, 67, 69, 71, 72, 60, 69, 67]
.do({ |midi| midi.yield });
});
dur = Routine({
[2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3]
.do({ |dur| dur.yield });
});
//looping (in this case, once)
r = Task({
var delta;
while {
delta = dur.next;
delta.notNil
} {
Synth(\smooth, [freq: midi.next.midicps, sustain: delta]);
delta.yield;
}
}).play(quant: TempoClock.default.beats + 1.0);
(Note that the examples given here can be more elegantly expressed using Patterns, see Tutorial #15)
(See the note on timing and latency at the bottom if you use this approach, or read more about Server Timing)
[back to top]Tutorial #15: Scheduling with Patterns
Use multiple datasets in a Task (with Patterns)
Data declaration still split out from looping, but here using Patterns to store the datasets:
(Note that the example given here can be more elegantly expressed using a Pattern-only approach, see below)
//datasets
midi = Pseq([60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1)
.asStream;
dur = Pseq([2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3], 1)
.asStream;
//looping (in this case, once)
r = Task({
var delta;
while {
delta = dur.next;
delta.notNil
} {
Synth(\smooth, [freq: midi.next.midicps, sustain: delta]);
delta.yield;
}
}).play(quant: TempoClock.default.beats + 1.0);
(PSeq means just spit out the values in declared order, as many times as the second argument - in this case, once)
(See the note on timing and latency at the bottom if you use this approach, or read more about Server Timing)
Use multiple datasets (Patterns-only approach)
Finally, the most elegant solution, using only Patterns:
(
SynthDef(\smooth, { |freq = 440, sustain = 1, amp = 0.5|
var sig;
sig = SinOsc.ar(freq, 0, amp) *
EnvGen.kr(Env.linen(0.05, sustain, 0.1), doneAction: 2);
Out.ar(0, sig ! 2)
}).add;
)
(
p = Pbind(
// the name of the SynthDef to use for each note
\instrument, \smooth,
// MIDI note numbers -- converted automatically to Hz
\midinote, Pseq([60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1),
// rhythmic values
\dur, Pseq([2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3], 1)
).play;
)
(Note that the syntax is now right down to the bone - no programming constructs, almost just data. For completeness, this time I have included the example SynthDef (\smooth))
Sample Pattern types: ordering
Many patterns take lists of values and return them in some order:
Return the list's values in order:
Pseq(list, repeats, offset)
Scramble the list into random order:
Pshuf(list, repeats)
Choose from the list's values randomly:
Prand(list, repeats)
Choose randomly, but never return the same list item twice in a row:
Pxrand(list, repeats)
Like Prand, but chooses values according to a list of probabilities/weights:
Pwrand(list, weights, repeats)
Sample Pattern types: generate based on parameters
In addition to these basic patterns, there is a whole set of random number generators that produce specific distributions, and also chaotic functions:
Arithmetic series, e.g., 1, 2, 3, 4, 5:
Pseries(start, step, length)
Geometric series, e.g., 1, 2, 4, 8, 16:
Pgeom(start, grow, length)
Random number generator, uses rrand(lo, hi) -- equal distribution:
Pwhite(lo, hi, length)
Random number generator, uses exprand(lo, hi) -- exponential distribution:
Pwhite(lo, hi, length)
Sample Pattern types: modify the output of other Patterns
Other patterns modify the output of value patterns. These are called FilterPatterns:
Repeat the pattern as many times as repeats indicates:
Pn(pattern, repeats)
Repeat individual values from a pattern n times. n may be a numeric pattern itself:
Pstutter(n, pattern)
Sample Pattern types: Patterns inside other Patterns
Other patterns modify the output of value patterns. These are called FilterPatterns:
Random numbers over a gradually increasing range (the upper bound on the random number generator is a stream that starts at 0.01, then proceeds to 0.02, 0.03 and so on, as the plot shows clearly):
p = Pwhite(0.0, Pseries(0.01, 0.01, inf), 100).asStream;
// .all pulls from the stream until it returns nil
// obviously you don't want to do this for an 'inf'
// length stream!
p.all.plot;
Order a set of numbers randomly so that all numbers come out before a new order is chosen, use Pn to repeat a Pshuf:
p = Pn(Pshuf([1, 2, 3, 4, 5], 1), inf).asStream;
p.nextN(15); // get 15 values from the pattern's stream
Patterns: further reading
This is just an intro - see more documentation to really understand it:
End of tutorial!
[back to top]Quick references
Here is a quick summary of methods and keyboard shortcuts found in the tutorial, plus a couple of helpful extras:
Commonly-used methods
.new | Create new object(s) from a static method. Note that e.g. Mix.new(x) is equiv to Mix(x) |
.post | Post the object to the console as a string |
.postln | As above with newline |
.value | Return the object's value (evaluate if it's a function, or just return the object itself) |
.play | To functions, 'play' means evaluate yourself and play the result on a server |
.choose | To arrays, means pick any element. The element could be another array (i.e. stereo) or a single value. Arrays can be mixed i.e.[[126,213],[415,314],231,344] |
!2 | Quickly make mono into stereo |
Commonly-used keyboard shortcuts
Cmd - \ | Bring the console window to the front |
Cmd-SHIFT-C | Clear the console |
Cmd - . | Stop all playback |
Cmd - D | Open the helpfile for whatever is currently selected |
Cmd - J | Open the definition file for whatever is currently selected |
Cmd - Y | (On a method) Show a list of classes which implement this method (polymorphism) |
Special arguments
index | Injects the current iterator into a loop, e.g.( |
midinote / freq | The argument midinote gets converted into a frequency value, and passed as the argument freq, e.g.(See also Tutorial #15: Scheduling with Patterns |
dur | Duration - see Tutorial #15: Scheduling with Patterns |
instrument | See Tutorial #15: Scheduling with Patterns |
Posted Monday, 28 May 2012
Getting Started with SuperCollider
I've been looking at SuperCollider (SC) to generate soundworks. I haven't used it previously and am learning from scratch.
In this post I'll briefly introduce what I've found out about SC, and describe the learning resources I've found.
A Quick Overview
SuperCollider runs as a client-server pair of applications, connected by OSC communication. The server (scsynth) does all the actual DSP work and produces all the sounds. The client (sclang) is an OO code-editing interface.
![]() |
| The SuperCollider logo |
From the client (sclang), you type and execute blocks of OO code. The objects you instantiate and manipulate are abstractions, which represent sound wave generators (and other items) which can be created and manipulated on the server (scsynth).
Behind the scenes, the client (sclang) interprets your interactions and communicates with the server (scsynth) on your behalf. The idea is to hide the messy but necessary details of interacting with scsynth via OSC messages, but to provide you with all the power to create and manipulate sound generators in a human-friendly form.
Coding in an OO client means you have full flexibility to build (and screw up, if you want) anything you want. A GUI, though easier to learn, wouldn't be able to provide that flexibility. On the other hand, coding in an OO client is much simpler than interacting directly with the server via OSC.
Anything which understands how to generate OSC commands for scsynth can act as a client -- sclang is just the one that comes bundled in the SuperCollider application (more on that here). Several people/clients can collaborate on a SC session by typing and executing blocks of code in their respective clients. However, the client-server architecture is still in play even if there is just one person creating a single soundwork on a single physical machine.
On sclang, you have access to a rich framework of sound-producing objects, such as 'unit generators' (UGens) and 'synth definitions' (SynthDefs). The SuperCollider language is based on SmallTalk and is designed to be really simple to use (once you understand some basics). It aims to be expressive, polymorphic, user-friendly and compact. Coding is quick, rather than strict -- the framework is littered with static object references for quick object instantiations, uses similar naming of class methods, code-minimizing language conventions, and syntax shortcuts wherever possible.
You can get a sense of just how compact and expressive SC is by looking at / playing some of these tracks. Each was generated using under 140 characters of SC code -- just the right size to fit inside a tweet.
![]() |
| Thor Magnusson at SC2012 (Photo: Steve Welburn) |
Learning resources
The first place to point out is this SC-bundled 'help landing page'. You're not going to learn from scratch there, but I'm sure you'll want to come back to that link again and again once you get started.
There is an active community around SC right now, and there are lots of learning resources available. The best place to get started is this tutorial by Scott Wilson and James Harkins. It's linked from the SuperCollider Sourceforge learning page, alongside a couple of other 'getting started' options, and also from the 'help landing page' mentioned above.
That tutorial is good to get started from scratch because it takes you through the language basics, and gently guides you through firing up the server and making some sounds. If, like me, you are short on time and haven't yet had a chance to fire up the server -- but are confident and comfortable with OO and basic DSP, you can just read through the whole tutorial in a few sittings on the subway and get a feel for the basics pretty quickly.
On the other hand, if you are less confident with OO and DSP, it's still a good tutorial and will guide you through. However it might seem to breeze through some stuff a bit quickly, in which case you could take an aside to study OO and DSP separately. However if you want to learn those subjects in the context of SC, one option is this physical book by Scott Wilson et al. I haven't read it but it looks comprehensive and will doubtless go at an easier pace, and give you time to experiment with examples before jumping on to new topics.
A quick aside for Windows users - often these tutorials are written for Mac. When you want to execute some code, they always tell you to hit ENTER (and not RETURN). If you are using Windows, and therefore probably gedit, you will actually need to hit CTRL + E, or some other environment-specific execution command.
Beyond the basics: IXI Software tutorials
Once you are past the building blocks, it can be a little daunting to find you have all this creative power at your fingertips, but don't know where to start. My friend Gene recommended these IXI Software tutorials (seemingly written by Thor Magnusson):
"This tutorial is not about programming SuperCollider, there are other tutorials that address that question. This tutorial is more about how to explore digital sound and synthesis using Supercollider as our tool. "
They start with a primer-style quick reference guide to the language and how to interact with the server (the same stuff you get from the introductory tutorials, but much quicker and with some good fleshing out). They go on to contextually discuss:
- Additive Synthesis
- Subtractive Synthesis
- AM, RM and FM Synthesis
- Envelopes - MIDI Keyboard
- Buffers and Samples
- Granular Synthesis
- Physical Modelling
- Fast Fourier Transform (FFT)
- Audio Effects and Filters
- Busses, Nodes, Groups and Signal Flow
- Musical Patterns on SCServer
- Musical Patterns in the SCLang
- Tuning Systems and Scales
- Graphical User Interfaces
This is much further than I have got yet, so I'll leave that there.
Beyond the basics: Designing Sound in SuperCollider
This WikiBook is a really interesting resource: Designing Sound in SuperCollider. In their own words:
"This book is an independent project based on Designing Sound by Andy Farnell, all about the principles and techniques needed to design sound effects for real-time synthesis. The original book provides examples in the PureData language - here we have re-created some of the examples using SuperCollider."
You may find it easier to browse the print version of the book, as you can copy and paste excerpts into SC to quickly try them out. Telephone bells, bouncing balls, rolling cans, fire, bubbles, water, rain - it sounds great and I'll certainly be back here soon to play with some of these examples.
Beyond the basics: Tour of UGens
This is a categorised quickref on all (well not all, but many) of the available unit generators in the SC release: Tour of Unit Generators. (There is also the UGen page).
It is actually one of the links off of the 'help landing page' mentioned earlier, but I've highlighted it here in it's own right as I have a feeling it will be an incredibly useful backbone to exploring how to construct your own custom SynthDefs. I haven't tried it yet, but just scrolling through the page: periodic sound sources, aperiodic sound sources, pulse, saw, blip, reverb, crackle, dust, LFO, pan, fade, delay, PlayBuf, granular synthesis, decay, EnvGen, FFT, gate...
...it's already starting to give me some ideas.
It mentions on that page that another way to tour the UGens is to use Help.gui. However there appears to be a problem with loading the help files under Windows in this release, and it is seemingly not resolved. Windows users seem to be an afterthought in some of the Open Source media environments, and I'll be switching to Mac as soon as my new hardware arrives next week.
Anyway - there's a bit tacked on the end of that page about 'building a sense of [artificial] space into a sound by setting up phase differences between the speakers'... and another one about so-called 'parallel structures'. Could be interesting.
References
Less useful in the beginning, but I'm sure could come in handy later... Someone has (automagically) collated all the help files from the standard SuperCollider distribution into one big PDF, The SuperCollider Help Book, which I'm sure could be useful at some stage to scroll through.
I'm still new here myself so I'll leave it there for now, but I'll come back and edit this page if and when I find more links that could be useful for getting started.
- Tags:
- Overview
- Thor Magnusson
- Designing Sound
- scsynth
- SynthDefs
- Andy Farnell
- James Harkins
- UGens
- SuperCollider
- Tutorial
- Learning Resources
- sclang
- Scott Wilson
- IXI Software
Posted Wednesday, 16 May 2012
Getting Started with OpenFrameworks
I've long known that openFrameworks was a really strong platform for my type of work. But my friend and collaborator Gene Kogan has now convinced me that the community is really a great place to be too, so I've decided to invest a little time.
Along with SuperCollider these are now the platforms I'm seriously considering in preparation for the I-Park residency.
Having used neither openFrameworks nor C++ before, there are some hurdles to jump. In this post I want to present my experience installing and getting started with the platform, so that it might help others who are starting out.
Which IDE to use?
The first choice you have to make is which IDE to use - in Windows this means choosing between Visual Studio and CodeBlocks.
Given that I already use Visual Studio for web application development, it seemed the natural choice. But after a couple of frustrating hours scanning scant support information online, I backtracked on that decision. CodeBlocks is the openFrameworks "preferred" IDE for Windows, articles are usually written with reference to it (when they are not written about XCode), and being new to C++ and openFrameworks I realised that I need those kinds of articles. Once I backtracked and switched to CodeBlocks, things became easier.
So that's the reason this article is written about CodeBlocks!
Step 1: Install CodeBlocks
So as discussed, step 1 becomes install CodeBlocks.
Step 2: Download openFrameworks for CodeBlocks
Download the appropriate (CodeBlocks) zip file from the openFrameworks download page. You can extract it to any directory you like. This will also become your working directory (you will build your apps inside here, it will be discussed in the tutorial below), so it's best to keep it somewhere easy to get to and easy to back up. For now, I just extracted it to a new directory 'C:\ofx\'.
Step 3: Set up CodeBlocks to work with openFrameworks
Follow the instructions on this page to set up CodeBlocks for openFrameworks. I found these to be simple and self-explanatory.
Step 4: Follow the openFrameworks tutorial
The openFrameworks guys have written a tutorial. If you're not sure yet what openFrameworks is, or what C++ is (and so on), you might appreciate the general introduction, but either way you should get going as quickly as possible with the tutorial.
Depending on your previous experience these links may seem overly basic or a little bit daunting. The tutorial goes through orientation, preferred practice (in terms of working directories and default project layout), right through to a fairly detailed discussion of basic programming constructs (the usual: sequence, selection, iteration, types and arrays etc. which are the subjects of so many programming books).
It also discusses how this applies in openFrameworks, and in media programming in general - all in one really long page! This is why it could seem a little daunting, although as an experienced programmer I was able to skip most of the material.
When you are done with that, you can zoom out to the main tutorials section and pick more topics from the left nav. I've included a couple of useful links from this section at the bottom of this post.
A bias to XCode
Unhelpfully, the IDE examples in the tutorial are all for XCode - for CodeBlocks all you get is 'Coming soon!' However if you read the XCode examples it's pretty easy to figure out how it applies to CodeBlocks.
Addons
The power of openFrameworks is in mixing and matching various community-contributed addons. In my case, to begin with, I'm interested in using my openFrameworks project for projection-mapping and real-time generative graphics, and talking to SuperCollider to achieve a Synesthetic effect.
I'll talk more about my experience with addons in the next post. In the meantime though, it's worth pointing out a few useful resources that may help:
- The openFrameworks wiki
- Introduction to openFrameworks for Processing users
- The site for sharing and finding openFrameworks addons, and a (currently quite poorly written) tutorial for how to add addons
- Creating your first particle system
- Slides from presentations. These can function as quick little intros to basic subjects, and cover the following topics: Sound, Video, Graphics, Events, 3D, OpenGL, Utils
- Vector maths
- For developers: Patterns, Layout & types of Classes, and Git & openFrameworks (also, a git / oF cheat sheet)
If you are trying to get started, good luck!
- Tags:
- Tutorial
- CodeBlocks
- openFrameworks
Posted Monday, 7 May 2012
Overtones, Harmonics and Additive Synthesis
For some upcoming projects I wanted to review some sound theory basics, and post some useful learning resources. I like to post links to videos (and tutorials), and write short summaries for future reference.
In this post, a quick intro to overtones, harmonics and additive synthesis, using a video lovingly prepared by Synth School:
And here are the notes:
Oscilloscope vs. Frequency Analysis view of a sine wave
One of the first things in the video is a comparison of the representation of a sine wave in an oscilloscope, with a sine wave in a frequency analysis (FFT) view:
![]() |
| Sine - Oscilloscope view |
![]() |
| Sine - FFT view |
This is a useful way to get to know the two views.
They both represent a number of samples from a waveform, say 512 or 1024 samples. The oscilloscope view is the result of taking those samples and displaying them fairly literally, one after the other, and plotting a line through them. The FFT view is the result of applying a Fourier transform which is designed to produce information about whichever frequencie are present across those samples.
FFT analysis can be thought of as the opposite of synthesis. In synthesis you might take a series of tones at different frequencies and combine them to produce a waveform which contains all of those frequencies. Fourier analysis in this context is designed to decompose that waveform and describe the individual frequencies that were originally combined to produce the waveform.
The oscilloscope can be thought of as a close-up version of the representation of a waveform you get in any DAW. Amplitude is plotted in the Y axis, and time is plotted in the X axis. The amplitude displayed here can be thought of as the position of a the surface of a loudspeaker, moving back and forth over a central 'resting position'.
In the FFT view, amplitude is plotted in the Y axis, and frequency is plotted in the X axis. By convention, lower-pitched frequencies are represented on the left, and higher-pitched frequencies are represented on the right.
The 'amplitude' in the FFT view is different - but related - to the 'amplitude' in the oscilloscope view. They are related because they both refer to loudness in the end, but where the oscilloscope describes amplitude in a way closer to the underlying physics, in a range from -1 to 1, the FFT describes amplitude in more of a human-understandable range (one we are familiar with from TV and stereo volume controls) in a range from 0 - 1.
Fundamental and overtones
A sine wave is called the 'pure' waveform because it is the only waveform type to contain only a single frequency. This frequency is called the fundamental frequency.
Other wave types always have additional frequencies on top of the fundamental. The lowest frequency of the sound is the basics on which the sound is built, and is called the fundamental frequency. The additional frequencies are called overtones.
![]() |
| Saw - Oscilloscope view |
![]() |
| Saw - FFT view |
Overtones are what constructs the sound's timbre. Overtones can be harmonic, or non-harmonic. Harmonic overtones support the fundamental frequency and keep it's tonality intact. Non-harmonic overtones result in noise, or sounds with ambiguous pitch.
Additive synthesis
A saw wave can be constructed by combining multiple sine waves, as demonstrated in the video. For a 'classic' saw tone, the amplitude of each added harmonic should be divided by it's harmonic count, i.e. the fundamental is divided by 1, the second harmonic is divided by 2, the third by 3 and so on.
![]() |
| Fundamental + 2nd harmonic |
![]() |
| Fundamental + 2nd and 3rd harmonics |
So a saw tone can be thought of as the combination of infinite harmonics, each divided by the harmonic count. Though in practice doing so would require infinite CPU power!
Square wave
A square wave is also constructed by adding harmonics, but this time only odd-numbered harmonics, i.e. by skipping the 2nd, 4th, 6th (etc) harmonics, and including those in between.
This is illustrated quite nicely in this old-school training video:
That's a nice intro to overtones, harmonics and additive synthesis. I'll be posting some more sound theory stuff soon.
- Tags:
- Tutorial
- FFT
- Frequency analysis
- Additive synthesis
- Oscilloscope
- Synth School
- Harmonics
- Sound theory
- Overtones
- Jaaga Residency (17)
- Jaaga (15)
- I-Park Residency (12)
- Process (12)
- V4W (10)
- Personal Development (10)
- Installation (10)
- VVVV (9)
- Field Research (8)
- Freemote Threshold (7)
- SuperCollider (7)
- Long (7)
- Freemote (7)
- Reflections (6)
- Audio / Visual (6)
- CAC Residency (6)
- Arduino (5)
- Tutorial (5)
- Influence (5)
- Max/MSP (5)
- Jaaga Sound and Lights (4)
- openFrameworks (4)
- Motor (4)
- Kinect (4)
- Projection Mapping (4)
- Portable Projection (4)
- Gravity (4)
- michael fairfax (3)
- Roman Moshensky (3)
- Rocks (3)
- Jee Soo Shin (3)
- Land Art (3)
- Picture This (2)
- Phenomenology (2)
- Git (2)
- Measure (2)
- Projection Bombing (2)
- Presentation (2)
- Creative Context (2)
- Natural Textures (2)
- Tess Martin (2)
- Scott Wilson (2)
- Alpha-Ville (2)
- Review (2)
- Untitled (Picture This) (2)
- Ralph Crispino (2)
- Cosm (2)
- Mac (2)
- Boaz Aharonovitch (2)
- C# (2)
- Mobile Projection (2)
- Memo Akten (2)
- Judith Stein (2)
- Generative Art (2)
- 3D (2)
All posts
May 2013
October 2012
September 2012
- Residency Begins at CAC Troy
- Installation Sketch at Open Studios
- Roman Moshensky's Mirror World
- Open Studios at I-Park
- Perception as a Creative Process
August 2012
- The I-Park Graveyard
- Scoping Out the Land
- Residency Begins at I-Park
- Residency at Contemporary Artists Center
July 2012
June 2012
- Stephen Lumenta's SC TextMate Bundle
- Adding OF Addons (ofxSuperCollider)
- Setting up SuperCollider with TextMate
- Switching to MacBook Pro
- QuickRef for SuperCollider
May 2012
- Getting Started with SuperCollider
- Getting Started with OpenFrameworks
- Overtones, Harmonics and Additive Synthesis
- Visit to Cold Spring
April 2012
January 2012
December 2011
- The Final Exhibition
- Playing with Particles
- Responsive Granular Sound
- Kinecting to the Network
- First Working Day
- Designs for Freemote
- Freemote Utrecht
- Untitled - Picture This (2011)
October 2011
- The Wider Context?
- Trading Time for Space
- Talk at Goldsmiths Digital Studios
- Intro to Marius Watz
- Practical Guide to Generative Art
September 2011
August 2011
June 2011
May 2011
April 2011
- Cosm, Collision Detection and Volume
- Vector-Base Amplitude Panning
- Intuition, and Direction of the Project
- Reflections: What is Jaaga?
- Going Further with Ambisonics
- Introduction to Ambisonics
- Surface (2010)
March 2011
- Servo Motors and Transistors
- Spinning a 12V DC Motor
- Spinning a 5V DC Motor
- First Week at Jaaga
- Presentation Style
- Beginning the Jaaga Fellowship
January 2011
November 2010
October 2010
September 2010










