Hello World, I Am Lonely Too
In this post I break down the creation of my track “Hello World, I Am Lonely Too” from my album “They Want To Make Your Body Move. I Want To Hold You Perfectly Still.”. The track was made entirely in Supercollider, although the final mix actually contains sections from two different renders of the song. While the structure of the song is highly scripted the individual note patterns and granular effects are controlled by random numbers. The first take I recorded had a number of parts I fell in love with but had a weak and meandering intro. I spliced the beginning of a different take in to correct this.
It all started with a SynthDef:
SynthDef(randomAddSynth, {|freq = 200, gate = 1, amp = 1, maxDelay = 1, maxAttack = 1, maxDecay = 1, maxHold = 1, maxRelease = 1| var numOfHarmonics = 10; var harmonics = freq * (2..numOfHarmonics); var delayArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxDelay)}); var attackArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxAttack)}); var decayArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxDecay)}); var holdArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxHold)}); var releaseArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxRelease)}); var sines = (SinOsc.ar(freq) * EnvGen.ar(Env.dadsr(Rand(maxDelay), Rand(maxAttack), Rand(maxDecay), Rand(maxHold), maxRelease), gate, doneAction:2)) / numOfHarmonics; harmonics.do{|frequency, i| sines = sines + (SinOsc.ar(frequency) * EnvGen.ar(Env.dadsr(delayArray[i], attackArray[i], decayArray[i], holdArray[i], releaseArray[i]), gate) / numOfHarmonics);}; Out.ar(0, sines!2 * amp); }).add;
At the time I wrote this code I was thinking back to an unfinished sketch I had created a long time ago using Processing and Beads. I started to think about how to create an additive synth that allowed each harmonic to have it’s own envelope. To avoid passing huge arrays of values for each envelope I decided to accept a maximum value for each envelope point and let the SynthDef randomize within the ranges. The original SynthDef used ASDR envelopes but things got really interesting when I switched to DADSR. The delays portion of the envelope added rhythmic movement to the sound:
To test the SynthDef you can run the following code:
x = Synth(randomAddSynth, [freq, 60, maxAttack, 10, maxDelay, 10]); x.set(gate, 0);
Next I came up with the following pattern that I thought created an interesting melody using the SynthDef
Pbind(*[instrument: randomAddSynth, note: Prand([2.5,5.5,3.5,4.3,4.7], inf), octave: Pseq([Prand([1,2], 8), Pwrand([1,2,3], [0.25, 0.5, 0.25], 10)], inf), dur: Pwhite(1,3,inf), amp: 0.4, maxDelay: 2, maxAttack: Pwrand([0.05, 4], [0.25, 0.75], inf), //Short or long attack maxDecay: 10, maxHold: 1, maxRelease: 10 ]).play;
At this point the piece sat untouched for a while as I knew I had a solid start but did not really know where to go with it.
After several weeks I found Carl Testa’s blog post “Approach to Working with SuperCollider in Improvisations”. While experimenting with the code he posted I came up with the following variation:
( { SynthDef(perc, { | out=0, freq=200, width=0.5, density=5, pan=0, amp=0.1, gate=1 | var sound, env; sound = RLPF.ar(PinkNoise.ar, freq, width/freq); sound = Dust.kr(density)*sound; env = EnvGen.kr(Env.perc, gate, doneAction:2); Out.ar(out, Pan2.ar(sound, pan, amp)); }).add; SynthDef(reverb, { |in=0, out=0, mix=0.5, room=0.5, damp=0.5, pan=0, amp=1 | var reverb; reverb = FreeVerb.ar(InFeedback.ar(in),mix,room,damp); Out.ar(out, Pan2.ar(reverb, pan, amp)); }).add; s.sync; ~mainOut = 0; ~verbBus = Bus.audio(s, 2); ~reverb = Synth.new(reverb, [out, ~mainOut, in, ~verbBus, room, 1, mix, 0.5, damp, 0.5]); Pdef(space, Pbind( instrument, perc, density, 100, freq, Pseq([Pseq([10,10000], 20), Pseq([10,100,1000,10000], 10)], inf), dur, Pwhite(0.1,0.2,inf), sustain, 0.1, amp, 0.5, out, ~verbBus ) ).play; Pdef(space).fadeTime = 30; }.fork; )
Much like the additive piece it got filed away in my archives until one day I happened to run the two pieces together and knew from that point on they could not be separated:
( { SynthDef(randomAddSynth, {|freq = 200, gate = 1, amp = 1, maxDelay = 1, maxAttack = 1, maxDecay = 1, maxHold = 1, maxRelease = 1| var numOfHarmonics = 10; var harmonics = freq * (2..numOfHarmonics); var delayArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxDelay)}); var attackArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxAttack)}); var decayArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxDecay)}); var holdArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxHold)}); var releaseArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxRelease)}); var sines = (SinOsc.ar(freq) * EnvGen.ar(Env.dadsr(Rand(maxDelay), Rand(maxAttack), Rand(maxDecay), Rand(maxHold), maxRelease), gate, doneAction:2)) / numOfHarmonics; harmonics.do{|frequency, i| sines = sines + (SinOsc.ar(frequency) * EnvGen.ar(Env.dadsr(delayArray[i], attackArray[i], decayArray[i], holdArray[i], releaseArray[i]), gate) / numOfHarmonics);}; Out.ar(0, sines!2 * amp); }).add; SynthDef(perc, { | out=0, freq=200, width=0.5, density=5, pan=0, amp=0.1, gate=1 | var filterNoise = RLPF.ar(PinkNoise.ar, freq, width / freq); var dustNoise = Dust.kr(density) * filterNoise; var env = EnvGen.kr(Env.perc, gate, doneAction:2); Out.ar(out, Pan2.ar(dustNoise, pan, amp)); }).add; SynthDef(reverb, { |in=0, out=0, mix=0.5, room=0.5, damp=0.5, pan=0, amp=1 | var reverb = FreeVerb.ar(InFeedback.ar(in),mix,room,damp); Out.ar(out, Pan2.ar(reverb, pan, amp)); }).add; s.sync; //Busses ~mainOut = 0; ~verbBus = Bus.audio(Server.default, 2); ~reverb = Synth.new(reverb, [out, ~mainOut, in, ~verbBus, room, 1, mix, 0.5, damp, 0.5]); Pdef(dust).fadeTime_(30).play; Pdef(addSyn).fadeTime_(30).play; //Patterns Pdef(addSyn, Pbind(*[instrument: randomAddSynth, note: Prand([2.5,5.5,3.5,4.3,4.7], inf), octave: Pseq([Prand([1,2], 8), Pwrand([1,2,3], [0.25, 0.5, 0.25], 10)], inf), dur: Pwhite(1,3,inf), amp: 0.4, maxDelay: 2, maxAttack: Pwrand([0.05, 4], [0.25, 0.75], inf), //Short or long attack maxDecay: 10, maxHold: 1, maxRelease: 10 ]); ); Pdef(dust, Pbind(*[instrument: perc, density: 100, freq: Pseq([Pseq([10,10000], 20), Pseq([10,100,1000,10000], 10)], inf), dur: Pwhite(0.1,0.2,inf), sustain: 0.1, amp: 0.2, out: ~verbBus ]); ); Pdef(dust, Pbind(*[instrument: perc, density: 10, freq: Pseq([Pseq([10,10000], 20), Pseq([10,100,1000,10000], 10)], inf), dur: Pwhite(0.1,0.2,inf), sustain: 0.1, amp: 0.2, out: ~verbBus ]); ); }.fork )
At this point all the basic pieces of the track were in place but I needed to figure out a way control the flow of the track. My decision was to schedule execution of changes to the Pdef and Pbindef. I use numerous postln statements to keep track of where I am in the track. I also split the track into 3 movements so that I could only play back the section that I was currently editing.
From here on I will let the code speak for itself. I welcome any questions or comments you may have.
Final Code:
( { //Variables var mainOut, verbBus, reverb, dust, syn, synOctaveSeq, movements = Array(3); //SynthDefs SynthDef(randomAddSynth, {|out = 0, freq = 200, gate = 1, amp = 1, maxDelay = 1, maxAttack = 1, maxDecay = 1, maxHold = 1, maxRelease = 1| var numOfHarmonics = 10; var harmonics = freq * (2..numOfHarmonics); var delayArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxDelay)}); var attackArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxAttack)}); var decayArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxDecay)}); var holdArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxHold)}); var releaseArray = Array.fill(numOfHarmonics, {arg i; Rand.new(0.0, maxRelease)}); var sines = (SinOsc.ar(freq) * EnvGen.ar(Env.dadsr(Rand(maxDelay), Rand(maxAttack), Rand(maxDecay), Rand(maxHold), maxRelease), gate, doneAction:2)) / numOfHarmonics; harmonics.do{|frequency, i| sines = sines + (SinOsc.ar(frequency) * EnvGen.ar(Env.dadsr(delayArray[i], attackArray[i], decayArray[i], holdArray[i], releaseArray[i]), gate) / numOfHarmonics);}; Out.ar(out, sines!2 * amp); }).add; SynthDef(perc, { | out=0, freq=200, width=0.5, density=5, amp=0.1, gate=1 | var pan = Rand.new(-1, 1); var filterNoise = RLPF.ar(PinkNoise.ar, freq, width / freq); var dustNoise = Dust.kr(density) * filterNoise; var env = EnvGen.kr(Env.perc, gate, doneAction:2); Out.ar(out, Pan2.ar(dustNoise, pan, amp)); }).add; SynthDef(reverb, { |in=0, out=0, mix=0.5, room=0.5, damp=0.5, pan=0, amp=1 | var reverb = FreeVerb.ar(InFeedback.ar(in, 2),mix,room,damp); Out.ar(out, Pan2.ar(reverb, pan, amp)); }).add; //Sync s.sync; //Busses mainOut = 0; verbBus = Bus.audio(Server.default, 2); reverb = Synth.new(reverb, [out, mainOut, in, verbBus, room, 1, mix, 0.5, damp, 0.5]); //Patterns dust = Pbind(*[ instrument: perc, density: 1, freq: Pseq([Pseq([10,10000], 20), Pseq([10,100,1000,10000], 10)], inf), dur: Pwhite(0.1,0.2,inf), sustain: 0.1, amp: 0.1, out: verbBus ]); synOctaveSeq = Pseq([Prand([1,2], 8), Pwrand([1,2,3], [0.25, 0.5, 0.25], 10)], inf); syn = Pbind(*[ instrument: randomAddSynth, note: Prand([2.5,5.5,3.5,4.3,4.7], inf), octave: synOctaveSeq, dur: Pwhite(1,3,inf), amp: 0.4, maxDelay: 2, maxAttack: Pwrand([0.05, 4], [0.25, 0.75], inf), //Short or long attack maxDecay: 10, maxHold: 1, maxRelease: 10, out: mainOut ]); //Movements movements.add({ "***Section 1***".postln; Pdef(dust).play; Pdef(dust, dust); "Slow Crackle".postln; 15.wait; // 00:15 Pbindef(dust, density, 100); "Speed Up".postln; 15.wait; // 00:30 Pdef(syn).fadeTime_(10).play; Pdef(syn, syn); "Syn".postln; 30.wait; // 01:00 Pbindef(dust, density, 10); Pbindef(syn, out, verbBus); "Verb".postln; 30.wait; // 01:30 Pbindef(dust, density, 100); Pbindef(syn, out, mainOut); "No verb".postln; 30.wait; // 02:00 Pdef(dust).stop; Pdef(syn).stop; "Full Stop".postln; 7.wait; // 02:07 }); movements.add({ "***Section 2***".postln; Pdef(dust, dust).play; Pbindef(dust, density, 1); "Dust Back".postln; 7.wait; // 00:00 Pbindef(dust, density, 20); "Faster".postln; 7.wait; // 00:07 Pbindef(dust, density, 50); "And Faster".postln; 7.wait; // 00:14 Pbindef(dust, density, 100); Pdef(syn, syn).play; Pbindef(syn, octave, synOctaveSeq + 1); "Syn back and Hi".postln; 30.wait; // 00:44 Pbindef(dust, density, 20); Pbindef(syn, out, verbBus); "Hi Verb".postln; 30.wait; // 01:14 Pbindef(syn, out, mainOut); "No verb".postln; Pbindef(dust, density, 1); "Slow the crackle".postln; Pbindef(syn, dur, 0.5); "Regulate Synth Rythum".postln; 30.wait; // 01:44 Pbindef(dust, density, 2); "Density: 2".postln; 2.wait; // 01:46 Pbindef(dust, density, 4); "Density: 4".postln; 2.wait; // 01:48 Pbindef(dust, density, 8); "Density: 8".postln; 2.wait; // 01:50 Pbindef(dust, density, 16); "Density: 16".postln; 2.wait; // 01:52 Pbindef(dust, density, 32); "Density: 32".postln; 2.wait; // 01:54 Pbindef(dust, density, 64); "Density: 64".postln; 2.wait; // 01:56 Pbindef(dust, density, 128); "Density: 128".postln; 1.wait; // 01:57 Pbindef(dust, density, 256); "Density: 256".postln; 1.wait; // 01:58 Pbindef(dust, density, 512); "Density: 512".postln; 1.wait; // 01:59 Pbindef(dust, density, 1024); "Density: 1024".postln; 1.wait; // 02:00 Pdef(dust).stop; Pdef(syn).stop; "Full Stop".postln; 7.wait; // 02:07 }); movements.add({ "***Section 3***".postln; Pdef(dust, dust).play; Pdef(syn, syn).play; Pbindef(syn, dur, 0.5); Pbindef(syn, octave, synOctaveSeq - 1); Pbindef(syn, out, verbBus); "Down 2 octaves and verbed".postln; 60.wait; // 01:00 Pbindef(dust, density, 10); Pbindef(dust, freq, Pseq([10,100,1000,5000], inf)); Pbindef(dust, dur, 0.25); "New noise signature".postln; 45.wait; // 01:45 Pbindef(syn, dur, Pwhite(1,3,inf),); Pbindef(syn, octave, synOctaveSeq); Pbindef(dust, density, 20); "Denser".postln; 45.wait; // 02:30 Pdef(dust).stop; Pdef(syn).stop; "Ring Out".postln; 10.wait; // 02:40 }); //Sequence Pdef(dust).fadeTime_(10); Pdef(syn).fadeTime_(10); "".postln;"-----Start Sequence-----".postln; movements[0].(); // 02:07 movements[1].(); // 04:14 movements[2].(); // 06:54 reverb.free; "-----End Sequence-----".postln; "".postln; }.fork )