I posted this in a Muff’s SC thread. This is an interactive homework assignment that I turned in a few years back. This isn’t the most in-depth SC tutorial, but it shows a learning process in a build-up manner. Just copy-paste the entire thing into the SC editor.
Anything labelled “AC” is a comment from my professor, Andres Cabrera:
//240A Theory Paper
//SuperCollider Composition by Michael Hetrick
//Started 12.03.12
//Finished 12.xx.12
//In this interactive paper, I will be exploring generative strategies in Supercollider. I am writing this paper simultaneously with my learning process,
//so all code you see is written as I learn it.
//Recently, I've been working a lot with my hardware modular synth. One of my favorite modules is Maths by Make Noise.
//The main concept of Maths is a looping AD envelope with many other tricks. I have found it to be invaluable in making music.
//I'm starting off with some example code for the Decay UGen:
play({ Decay.ar(Impulse.ar(XLine.kr(1,50,20), 0.25), 0.2, PinkNoise.ar, 0) });
//Similarly, the Decay2 documentation contains a much more pleasant sine implementation.
{ Decay.ar(Impulse.ar(XLine.kr(1,50,20), 0.25), 0.2, FSinOsc.ar(600), 0) }.play;
//Both of these examples use an XLine object to take the AD envelope from subsonic frequencies, through morphosis into AM synthesis.
//Annoylingly, both of these examples only use the left channel.
//While the morphosis is a very cool effect, I want to do something more rhythmic in stereo. I am starting with the following line:
{ Decay.ar(Impulse.ar([1.50, 1.25], 0.25), 0.3, FSinOsc.ar(600), 0)*0.3 }.play;
//This creates a short percussive loop in stereo. To achieve the stereo effect, I have used the [ , ] notation inside of the Impulse arguments.
//Next, I want to set up stereo notes:
{ Decay.ar(Impulse.ar([1.50, 1.25], 0.25), 0.3, FSinOsc.ar([600,300]), 0)*0.3 }.play;
//This is more interesting, but still quite boring. To offset this, I have set up three envelopes to be played simultaneously.
{ Decay.ar(Impulse.ar([1.50, 1.25], 0.25), 0.3, FSinOsc.ar(600), 0)*0.3 }.play;
{ Decay.ar(Impulse.ar([0.75, 1.75], 0.25), 0.3, FSinOsc.ar(300), 0)*0.3 }.play;
{ Decay.ar(Impulse.ar([2.50, 1.00], 0.25), 0.3, FSinOsc.ar(400), 0)*0.3 }.play;
//Triggering these simultaneously creates all sorts of rhythms as the patterns go in and out of sync.
//Okay, so now we need changing notes. It seems the best way to do this would be to create a SynthDef, and trigger each impulse manually:
SynthDef.new("blip", { |leftPitch = 440, rightPitch = 440, amp = 0.3, decay = 0.25, leftFreq=0.0, rightFreq=0.0|
var sig;
sig = Decay.ar(Impulse.ar([leftFreq, rightFreq], 0.0), decay, FSinOsc.ar([leftPitch, rightPitch]), 0)*amp;
Out.ar(0, sig ! 2); // sig ! 2 is the same as [sig, sig]
}).play;
x = Synth.new("blip");
// AC: Remember to free the synth! You can use the doneAction in envelope generators, but it can't be done here! (OK I see you've solved it below)
//That took a lot of trial and error! If Impulse's frequency is set to 0, it only releases one impulse. This now gives me the flexibility
//to sequence individual blips or full pulse streams. For example:
x = Synth.new("blip",["leftPitch", 600, "rightPitch", 400]); //blip chord
x = Synth.new("blip",["leftPitch", 600, "rightPitch", 400, "leftFreq", 200, "rightFreq", 20, "amp", 0.1]); //Stereo AM synth
// AC: I'm trying to figure out why this sounds buzzy. Do you know? I suspect there is a sudden jump in amplitude when the Decay is retriggered.
//We learned about Routines in class, which are something I haven't tried before. Let's attempt to create a simple stereo sequence.
r = Routine({
a = Synth.new("blip",["leftPitch", 600, "rightPitch", 400]).yield;
b = Synth.new("blip",["leftPitch", 300, "rightPitch", 300]).yield;
c = Synth.new("blip",["leftPitch", 900, "rightPitch", 200]).yield;
d = Synth.new("blip",["leftPitch", 800, "rightPitch", 400]).yield;
a.free; b.free; c.free; d.free;
});
r.next;
r.reset;
//Cool! We have two melodies (one on each channel). Alternatively, we can use a task:
(
t = Task({
loop {
[600, 300, 900, 800].do({ |seqFreq|
Synth("blip", [ amp: 0.2, leftPitch:seqFreq]);
0.125.wait;
});
}
}).play;
)
t.stop;
t.play;
//I like that! I'm wondering what will happen if I do a double do loop:
(
t = Task({
loop {
[400, 300, 200, 300].do({ |rightSeqFreq|
[600, 300, 900, 800].do({ |leftSeqFreq|
x = Synth("blip", [ amp: 0.2, leftPitch:leftSeqFreq, rightPitch:rightSeqFreq]);
0.125.wait;
x.free;
});
});
}
}).play;
)
t.stop;
t.play;
//I turned the Synth part into "x" so that I could free it after every loop. I did this because the RAM and CPU usage were
//growing with every repeat. This is now an efficient, looping sequence!
//The SC sequencing tutorial is now trying to convince me to use patterns. This seems like it would be a cleaner way of sequencing
//longer patterns with more variation.
(
var leftNoteSequence, rightNoteSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
t = Task({
loop {
x = Synth("blip", [ amp: 0.2, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next]);
0.125.wait;
x.free;
}
}).play;
)
//Now we're talking! Because of the uneven PSeq size, I now have a 30-event sequence. Let's see if we can do something cool
//with changing the frequency of the Impulse generator.
//First, let's redefine the SynthDef to include stereo amplitude control:
SynthDef.new("blip", { |leftPitch = 440, rightPitch = 440, ampL = 0.3, ampR = 0.3, decay = 0.25, leftFreq=0.0, rightFreq=0.0|
var sig;
sig = Decay.ar(Impulse.ar([leftFreq, rightFreq], 0.0), decay, FSinOsc.ar([leftPitch, rightPitch]), 0)*[ampL, ampR];
Out.ar(0, sig ! 2); // sig ! 2 is the same as [sig, sig]
}).play;
//Now, let's make a new sequence with pauses
(
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
t = Task({
loop {
x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next]);
0.125.wait;
x.free;
}
}).play;
)
//Cool! Here it is with different Impulse frequencies:
(
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
var leftImpSequence, rightImpSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
leftImpSequence = Pseq([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 12.0],inf).asStream;
rightImpSequence = Pseq([0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 14.0, 0.0, 0.0, 0.0, 0.0],inf).asStream;
t = Task({
loop {
x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next, leftFreq: leftImpSequence.next, rightFreq: rightImpSequence.next]);
0.125.wait;
x.free;
}
}).play;
)
//A bit clicky. Let's redefine our Synth to use Decay2, which has an attack time.
// AC: This is the solution...
(
SynthDef.new("blip", { |leftPitch = 440, rightPitch = 440, ampL = 0.3, ampR = 0.3, decay = 0.25, leftFreq=0.0, rightFreq=0.0|
var sig;
sig = Decay2.ar(Impulse.ar([leftFreq, rightFreq], 0.0), 0.01, decay, FSinOsc.ar([leftPitch, rightPitch]), 0)*[ampL, ampR];
Out.ar(0, sig ! 2); // sig ! 2 is the same as [sig, sig]
}).play;
)
//And now, let's use a shorter decay time, and let's change the length of time between loops
(
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
var leftImpSequence, rightImpSequence;
var loopLengthSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
leftImpSequence = Pseq([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 12.0],inf).asStream;
rightImpSequence = Pseq([0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 14.0, 0.0, 0.0, 0.0, 0.0],inf).asStream;
loopLengthSequence = Pseq([0.125, 0.125, 0.125, 0.25, 0.25, 0.125],inf).asStream;
t = Task({
loop {
x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next, leftFreq: leftImpSequence.next, rightFreq: rightImpSequence.next, decay: 0.15]);
loopLengthSequence.next.wait;
x.free;
}
}).play;
)
//Now that I have a cool melodic sequence, I should put together a hi-hat pattern
(
SynthDef.new("hihat", { |ampL = 0.3, ampR = 0.3, decay = 0.10, leftFreq=0.0, rightFreq=0.0|
var sig;
sig = Decay2.ar(Impulse.ar([leftFreq, rightFreq], 0.0), 0.01, decay, WhiteNoise.ar, 0)*[ampL, ampR];
Out.ar(0, sig ! 2);
}).play;
)
//Adding it to the melodic sequence:
(
var hiHatSequence;
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
var leftImpSequence, rightImpSequence;
var loopLengthSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
leftImpSequence = Pseq([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 12.0],inf).asStream;
rightImpSequence = Pseq([0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 14.0, 0.0, 0.0, 0.0, 0.0],inf).asStream;
loopLengthSequence = Pseq([0.125, 0.125, 0.125, 0.25, 0.25, 0.125],inf).asStream;
hiHatSequence = Pseq([0.3, 0.1, 0.3, 0.0, 0.3, 0.0, 0.3, 0.0, 0.0, 0.1, 0.1, 0.0, 0.3],inf).asStream;
t = Task({
loop {
x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next, leftFreq: leftImpSequence.next, rightFreq: rightImpSequence.next, decay: 0.15]);
y = Synth("hihat", [ ampL: hiHatSequence.next, ampR: hiHatSequence.next]);
loopLengthSequence.next.wait;
x.free; y.free;
}
}).play;
)