• jon@schemawound.com

Sinusoid

Today Waxen Wings has released my track ‘Sinusoid’ on the album ’Give Me A Sine’. The track was produced entirely using Supercollider.  I decided to take a couple minutes to break down how the track was created the same way I did with my track ’The Tunnel’.

The submission guidelines for the compilation:

All songs should be written using ONLY sine waves in their creation. All oscillations, modulations, lfo’s, envelopes, etc, use only sine waves. No samples or outside source audio are permitted on this releases, unless of course the samples are of pure sine waves.

I missed the part where your envelopes should be sines only so my final code technically doesn’t apply but I didn’t notice this until after the compilation was released.

I came up with the following two tweets as potential starting points but neither of them fit the sound I was looking for.

{(CombC.ar([Mix(SinOsc.ar((40..50)*7.23,1..10)/10)),Mix(SinOsc.ar((40..50)*6.41,(1..10)/10))],10,SinOsc.ar(0.0001,0,10),2)*0.02)}.play
{CombC.ar(Mix(SinOsc.ar((1..20)*145.12))*SinOsc.ar([SinOsc.ar(0.14,0,40),SinOsc.ar(0.19,0,37)])*SinOsc.ar([0.023,0.012]),1,0.1,10)*0.09}.play

This one began to get closer to the actual sound I wanted but still wasn’t quite right.

{CombC.ar(Mix(SinOsc.ar((1..20)*6.12))*SinOsc.ar([SinOsc.ar(15.4,0,20),SinOsc.ar(1.9,0,37)])*SinOsc.ar([500,400]),1,0.01,10)*0.01}.play

After some further tweaking of the numbers I ended up coming up with the following code.

{CombC.ar(Mix(SinOsc.ar((1..500)))*SinOsc.ar([SinOsc.ar(150,0,20),SinOsc.ar(SinOsc.kr(0.1,0,100,100),0,37)])*SinOsc.ar([50]),1,0.7,9).tanh}.play

The sound of 500 sine waves falling in and out of sync with each other was getting much closer to the sound I was looking for so I began unpacking that code to get a better idea of what I had going on inside it.

(
{
	CombC.ar(
		Mix(SinOsc.ar((1..500)))
		* SinOsc.ar([
			SinOsc.ar(150,0,20),
			SinOsc.ar(
				SinOsc.kr(0.1,0,100,100),
			0,
			37
			)
		])
		*SinOsc.ar([50]),1,0.7,9
	).tanh
}.play
)

I then took it a step further trying to logically label the parts that made up the sound.

(
{
	//Many Sines
	var freqArray = (1..500); 
	var manySines = Mix(SinOsc.ar(freqArray));
	//Mod1
	var leftMod1Freq = SinOsc.ar(150,0,20);
	var rightMod1Freq = SinOsc.ar(SinOsc.kr(0.1,0,100,100),0,37);
	var mod1 = SinOsc.ar([leftMod1Freq,rightMod1Freq]);
	//Mod2
	var mod2 = SinOsc.ar(50);
	//Sum and FX
	var sinSum = manySines * mod1 * mod2;
	var comb = CombC.ar(sinSum,1,0.7,9);
	var dist = comb.tanh;
	//Output
	Out.ar(0, dist);
}.play
)

Next I turned the whole sound into a SynthDef with parameters. I made sure the default values kept the basic sound that I liked.

(SynthDef(Sinusoid, {
	|
		out = 0,					freqArrayMult = 1,		mod1FreqRLfoFreq = 0.1,	
		mod1FreqRLfoDepth = 100,	mod1FreqRLfoOffset = 100,	mod2Freq = 50,	
		combMaxDelay = 0.7, 		combDelay = 0.7, 			combDecay = 9
	|
	//Many Sines
	var freqArray = (1..500) * freqArrayMult; 
	var manySines = Mix(SinOsc.ar(freqArray));
	//Mod1
	var mod1FreqL = SinOsc.kr(150, 0, 20);
	var mod1FreqRLfo = SinOsc.kr(mod1FreqRLfoFreq, 0, mod1FreqRLfoDepth, mod1FreqRLfoOffset);
	var mod1FreqR = SinOsc.kr(mod1FreqRLfo, 0, 37);
	var mod1 = SinOsc.ar([mod1FreqL, mod1FreqR]);
	//Mod2
	var mod2 = SinOsc.ar(mod2Freq);
	//Sum and FX
	var sinSum = manySines * mod1 * mod2;
	var comb = CombC.ar(sinSum, combMaxDelay, combDelay, combDecay);
	var dist = comb.tanh;
	//Output
	Out.ar(out, dist);
}).add
)

At this point I began to run into error messages stating “Exceeded number of interconnect buffers”. After doing some digging I found this could be solved by increasing the number of WireBufs before the server was started. I added the following line to my startup file.

s.options.numWireBufs = 1024;

The synth could be tested with the following code

x = Synth(Sinusoid);

I made a few more changes to the SynthDef adding gating and an envelope

SynthDef(Sinusoid, {
	|
		out = 0,					gate = 1,					amp = 1,					freqArrayMult = 1,		
		mod1FreqRLfoFreq = 0.1,		mod1FreqRLfoDepth = 100,	mod1FreqRLfoOffset = 100,	
		mod2Freq = 50,				combDelay = 0.7, 			combDecay = 9,				
		attack = 0.001 				release = 0.5
	|
	//
	var combMaxDelay = 10;
	//Many Sines
	var freqArray = (1..50) * freqArrayMult; 
	var manySines = Mix(SinOsc.ar(freqArray));
	//Mod1
	var mod1FreqL = SinOsc.kr(150, 0, 20);
	var mod1FreqRLfo = SinOsc.kr(mod1FreqRLfoFreq, 0, mod1FreqRLfoDepth, mod1FreqRLfoOffset);
	var mod1FreqR = SinOsc.kr(mod1FreqRLfo, 0, 37);
	var mod1 = SinOsc.ar([mod1FreqL, mod1FreqR]);
	//Mod2
	var mod2 = SinOsc.ar(mod2Freq);
	//Sum and FX
	var sinSum = manySines * mod1 * mod2;
	var comb = sinSum; //+ CombC.ar(sinSum, combMaxDelay, combDelay, combDecay);
	var dist = comb.tanh;
	var env = dist * Linen.kr(gate, attack, amp, release, doneAction: 2);
	//Output
	Out.ar(out, env);
}).add;

After doing some brief experimentation with creating a GUI and trying to play it as a drone synth I realized I wanted to take things in a different direction. Droning sine waves seemed to be the obvious direction given the theme of the compilation and I wanted to try to do something a little more percussive. First I created the following two pieces trying to generate random percussion

(//Diversion 1
Pbind(*[
	instrument:			Sinusoid,
	freqArrayMult:		Pseq((1..12), inf),
	dur:				Pwhite(0.1, 0.3, inf),
	mod2Freq:			Pwhite(30, 1000, inf),
	mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
	mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
	mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
	release:			0.001
]).play
)
(//Diversion 2
p = Pbind(*[
	instrument:			Sinusoid,
	freqArrayMult:		Pseq((1..12), inf),
	dur:				0.5,
	mod2Freq:			Pwhite(30, 1000, inf),
	mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
	mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
	mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
	release:			0.001
]);
o = Pbind(*[
	instrument:			Sinusoid,
	freqArrayMult:		Pseq((1..12), inf),
	dur:				0.3,
	mod2Freq:			Pwhite(30, 1000, inf),
	mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
	mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
	mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
	release:			0.001
]);
Ppar([p,o]).play;
)

This next attempt really started to hit closer at what I was looking for but still wasn’t quite right

(//Diversion 3
	~sinuClock = TempoClock.new(1);

	~phrases = (
		main1: Pbind(*[
			instrument:			Sinusoid,
			freqArrayMult:		Pseq([4], 4),
			dur:				1,
			release:			1,
			mod2Freq:			50
		]),
		main2: Pbind(*[
			instrument:			Sinusoid,
			freqArrayMult:		Pseq([4, 1], 2),
			dur:				1,
			release:			1,
			mod2Freq:			50
		]),
		fill1: Pbind(*[
			instrument:			Sinusoid,
			freqArrayMult:		Pseq([4], 4),
			dur:				0.5,
			release:			0.5,
			mod2Freq:			100
		]),
	);

	~sinuSequence = Psym(
		Pseq(#[
				main1, 
				fill1, 
				main2, 
				fill1
			], inf
		), 
		~phrases
	);


	Ppar([~sinuSequence]).play(~sinuClock)
)

The next version came together rather quickly and you can hear most of the elements that made it into the final track. My basic concept was to make patterns using the sinusoid synth to make up the different instruments on the track: bass, hit hat, low drone.

(
	//Measures
	var hat = Pbind(*[
		instrument:			Sinusoid,
		amp:				0.125,
		freqArrayMult:		1,
		dur:				Pseq([0.1], 9),
		mod2Freq:			Pwhite(60, 6000, inf),
		mod1FreqRLfoFreq:	0.01,
		mod1FreqRLfoDepth:	Pwhite(1000, 3000, inf),
		mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
		release:			0.1
	]);
	var hatX2 = Pbind(*[
		instrument:			Sinusoid,
		amp:				0.125,
		freqArrayMult:		1,
		dur:				Pseq([0.05], 18),
		mod2Freq:			Pwhite(60, 6000, inf),
		mod1FreqRLfoFreq:	0.01,
		mod1FreqRLfoDepth:	Pwhite(1000, 3000, inf),
		mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
		release:			0.01
	]);
	var bass = Pbind(*[
		instrument:			Sinusoid,
		amp:				0.25,
		freqArrayMult:		1,
		dur:				Pseq([0.3, 0.3, 0.3], 1),
		mod2Freq:			Pwhite(30, 300, inf),
		mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
		mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
		mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
		release:			0.001
	]);
	var lowtone = Pbind(*[
		instrument:			Sinusoid,
		amp:				0.25,
		freqArrayMult:		1,
		dur:				Pseq([0.9], 1),
		mod2Freq:			Pwhite(30, 600, inf),
		mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
		mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
		mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
		release:			1
	]);
	var lowtoneX2 = Pbind(*[
		instrument:			Sinusoid,
		amp:				0.25,
		freqArrayMult:		1,
		dur:				Pseq([0.9], 1),
		mod2Freq:			3000,
		mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
		mod1FreqRLfoDepth:	Pwhite(500, 1000, inf),
		mod1FreqRLfoOffset:	100,
		release:			1
	]);

	//Patterns
	var pat1 = lowtone;
	var pat2 = Ppar([bass, lowtone]);
	var pat3 = Ppar([hat, bass, lowtone]);
	var pat4 = Ppar([hatX2, bass, lowtone]);
	var pat5 = Ppar([lowtone, lowtoneX2]);

	//Sections
	var intro = Pn(pat1, 8);
	var verse = Pseq([
						Pn(pat2, 8),
						Pn(pat3, 7), 
						pat4
					]);
	var drop = Pn(pat5, 2);

	//Song
	var song = Pseq([intro, verse, drop, verse]);

	song.play;
)

From this point on there was a lot of tweaking to get things exactly the way I wanted them but there was not any major changes from the previous version. I brought in a kick SynthDef I had lying around to fill in the low end (I may not be the original author of this kick SynthDef, I cannot find any notes of where I found it from). I created a quick reverb SynthDef and created groups and an FX bus so I could easily route sounds to the reverb. I also changed my basic Pbinds into functions that would produce Pbinds. The common feature of each of the functions is a parameter named beatsPerMeasure. This parameter specifies how many beats each measure should be subdivided into. Perhaps there is a better method to set this up using patterns without functions but at this point I was getting close to the deadline and needed to get the piece finished. One convenient thing about the way this piece was structured is that when I wanted to test a short piece I could replace the song.play command at the end with the relevant section to play. NOTE: I think the groups on this are not working as intended, because of the deadline I did not bother debugging this further.

(
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Sinusoid -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	SynthDef(Sinusoid, {
		|
			out = 0,					gate = 1,					amp = 1,					freqArrayMult = 1,		
			mod1FreqRLfoFreq = 0.1,		mod1FreqRLfoDepth = 100,	mod1FreqRLfoOffset = 100,	
			mod2Freq = 50,				combDelay = 0.7, 			combDecay = 9,				
			attack = 0.001 				release = 0.5
		|
		//
		var combMaxDelay = 10;
		//Many Sines
		var freqArray = (1..50) * freqArrayMult; 
		var manySines = Mix(SinOsc.ar(freqArray));
		//Mod1
		var mod1FreqL = SinOsc.kr(150, 0, 20);
		var mod1FreqRLfo = SinOsc.kr(mod1FreqRLfoFreq, 0, mod1FreqRLfoDepth, mod1FreqRLfoOffset);
		var mod1FreqR = SinOsc.kr(mod1FreqRLfo, 0, 37);
		var mod1 = SinOsc.ar([mod1FreqL, mod1FreqR]);
		//Mod2
		var mod2 = SinOsc.ar(mod2Freq);
		//Sum and FX
		var sinSum = manySines * mod1 * mod2;
		var comb = sinSum; //+ CombC.ar(sinSum, combMaxDelay, combDelay, combDecay);
		var dist = comb.tanh;
		var env = dist * Linen.kr(gate, attack, amp, release, doneAction: 2);
		//Output
		Out.ar(out, env);
	}).add;

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ELEKTRO KICK -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	SynthDef(ElektroKick, { 
		|
			out = 0,		gate = 1,		amp = 1,			freqArrayMult = 1,		
			basefreq = 50,	envratio = 3, 	freqdecay = 0.02, 	ampdecay = 0.5
		|
		var fenv = EnvGen.ar(Env([envratio, 1], [freqdecay], exp), 1) * basefreq;
		var aenv = EnvGen.ar(Env.perc(0.005, ampdecay), 1, doneAction:2);
		var output = SinOsc.ar(fenv, 0.5pi, aenv) * amp;
		Out.ar(out, output!2);
	}).add;

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- VERB -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	SynthDef(Verb, {
		arg 	out = 0,	in,
			mix = 0.25,	room = 0.15,	damp = 0.5;
	
		var input, verb;
		
		input = In.ar(in);
		verb = FreeVerb.ar(input, mix, room, damp);
		Out.ar(out, verb!2);
	}).add;
)

(
	//Groups and Busses
	var sourceGroup = Group.new;
	var fxGroup = Group.after(~sourceGroup);
	var verbBus = Bus.audio(s, 2);
	var mainOut = 0;
	var verb = Synth.tail(~fxGroup, Verb, [in, verbBus, out, mainOut, mix, 1, room, 1]);

	//Song Variables
	var bar = 0.94;
	var qNote = bar/4;
	var eNote = bar/8;

	//Mix
	var finalAmp 	= 0.1;
	var hatAmp 		= 0.6 * finalAmp;
	var bassAmp 	= 0.8 * finalAmp;
	var loToneAmp 	= 0.8 * finalAmp;
	var hiWhineAmp 	= 0.7 * finalAmp;
	var eKickAmp 	= 2.1 * finalAmp;

	//Basic Patterns
	var hat = {|beatsPerMeasure = 9, freqArrayMult = 1|
		Pbind(*[instrument: Sinusoid, amp: hatAmp, group: sourceGroup,
			dur:				Pseq([bar / beatsPerMeasure], beatsPerMeasure),
			freqArrayMult:		Pxrand((1..12), inf),
			mod2Freq:			Pwhite(60, 6000, inf),
			mod1FreqRLfoFreq:	0.01,
			mod1FreqRLfoDepth:	Pwhite(1000, 3000, inf),
			mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
			release:			Pkey(dur)
		])
	};
	var bass = {|beatsPerMeasure|
		Pbind(*[instrument: Sinusoid, amp: bassAmp, group: sourceGroup,
			dur:				Pseq([bar / beatsPerMeasure], beatsPerMeasure),
			mod2Freq:			Pwhite(30, 300, inf),
			mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
			mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
			mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
			release:			0.001
		])
	};
	var lowtone = {|beatsPerMeasure = 1, attack = 0.001, out = 0|
		Pbind(*[instrument: Sinusoid, amp: loToneAmp, group: sourceGroup,
			dur:				Pseq([bar / beatsPerMeasure], beatsPerMeasure),
			mod2Freq:			Pwhite(30, 400, inf),
			mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
			mod1FreqRLfoDepth:	Pwhite(10, 300, inf),
			mod1FreqRLfoOffset:	Pwhite(10, 300, inf),
			attack:				attack,
			release:			Pkey(dur),
			out:				out
		])
	};
	var lowtoneLong = Pbind(*[instrument: Sinusoid, amp: loToneAmp, group: sourceGroup,
		dur:				Pseq([bar*8], 1),
		freqArrayMult:		3,
		mod2Freq:			50,
		mod1FreqRLfoFreq:	0.1,
		mod1FreqRLfoDepth:	100,
		mod1FreqRLfoOffset:	100,
		release:			3
	]);
	var hiWhine = {|out = 0|
		Pbind(*[instrument: Sinusoid, amp: hiWhineAmp, group: sourceGroup,
			dur:				Pseq([bar], 1),
			mod2Freq:			2000,
			mod1FreqRLfoFreq:	Pwhite(0.1, 0.3, inf),
			mod1FreqRLfoDepth:	100,
			mod1FreqRLfoOffset:	100,
			release:			Pkey(dur),
			out:				out
		])
	};
	var elektroKick = {|beatsPerMeasure = 1|
		Pbind(*[instrument: ElektroKick, amp: eKickAmp, group: sourceGroup,
			dur:				Pseq([bar / beatsPerMeasure], beatsPerMeasure),
			basefreq:			Pwhite(70, 75),
			ampdecay:			2,
			envratio:			1,
			freqdecay:			1
		])
	};

	//8 Bar Patterns
	var loTonePat = [
		Pn(lowtone.(1), 8),					//loTone pattern 0 - 8 bars
		Pn(lowtone.(1, bar), 8),				//loTone pattern 1 - 8 bars
		Pn(lowtone.(1, out:verbBus), 8)	//loTone pattern 2 - 8 bars
	];

	var hiWhinePat = [
		Pn(hiWhine.(verbBus), 8),	//hiWhine pattern 0 - 8 bars
		Pn(hiWhine.(mainOut), 8)	//hiWhine pattern 0 - 8 bars
	];

	var hatPat = [
		Pseq([//hat pattern 0 - 8 bars
			Pn(hat.(9), 7),		hat.(11)
		]),	
		Pseq([//hat pattern 1 - 8 bars
			hat.(8), 			hat.(9,(1..12)),	hat.(9), 			hat.(7,(1..12)),
			hat.(6,(1..12)),	hat.(12,(1..12)),	hat.(6,(1..12)),	hat.(24,(1..12))
		]),
		Pseq([//hat pattern 2 - 8 bars
			hat.(8), 			hat.(3),			hat.(6),			hat.(9),
			hat.(8,(1..12)),	hat.(3,(1..12)),	hat.(6,(1..12)),	hat.(12,(1..12))
		]),
		Pseq([//hat pattern 3 - 8 bars
			hat.(9), 			hat.(8),			hat.(7),			hat.(8),
			hat.(9,(1..12)),	hat.(8,(1..12)),	hat.(16,(1..12)),	hat.(32,(1..12))
		])
	];

	var bassPat = [
		Pn(bass.(3), 8),	//bass pattern 0 - 8 bars
		Pseq([				//bass pattern 1 - 8 bars
			bass.(4),	Pn(bass.(3),3)
		], 2),
		Pseq([				//bass pattern 2 - 8 bars
			bass.(4),	Pn(bass.(3),3),		
			bass.(4),	Pn(bass.(3),2), 	bass.(5)
		]),
		Pseq([				//bass pattern 3 - 8 bars
			bass.(4), 	bass.(3.5), 		bass.(3),		bass.(3.5),
			bass.(4), 	bass.(3), 			bass.(6),		bass.(7)
		]),
		Pseq([				//bass pattern 4 - 8 bars
			bass.(4),	Pn(bass.(3), 7)
		]),
	];

	var kickPat = [
		Pn(elektroKick.(1), 8),		//kick pattern 0 - 8 bars
		Pn(elektroKick.(2), 8)	//kick pattern 1 - 8 bars
	];

	var drop = [
		Pn(Ppar([lowtone.(1), hiWhine.(verbBus)]), 2),										//Drop Pattern 0 - 2 bars
		Pn(Ppar([lowtone.(1), hiWhine.(verbBus), elektroKick.(1)]), 2),						//Drop Pattern 1 - 2 bars
		Pn(Ppar([lowtone.(1), hiWhine.(verbBus), elektroKick.(1), hiWhine.(mainOut)]), 2),	//Drop Pattern 2 - 2 bars
	];

	//Song
	var song = Pseq([
								loTonePat[0], 				
		Ppar([	bassPat[0], 	loTonePat[0]																				]), 
		Ppar([	bassPat[0], 	loTonePat[0],					hatPat[0]													]), 
		drop[0], 
		Ppar([	bassPat[0], 	loTonePat[0], 								kickPat[0]										]), 
		Ppar([	bassPat[0], 	loTonePat[0],					hatPat[0], 	kickPat[0]										]),
		drop[1],
		Ppar([	bassPat[1], 									hatPat[2], 	kickPat[0]										]), 
		Ppar([	bassPat[2], 	loTonePat[0], 					hatPat[2], 	kickPat[0]										]),
		drop[2],
		Ppar([	bassPat[1], 									hatPat[2], 	kickPat[0],		hiWhinePat[0]					]), 
		Ppar([	bassPat[2], 	loTonePat[0], 					hatPat[2], 	kickPat[0]										]),
		Ppar([	bassPat[1], 					loTonePat[2],	hatPat[2], 	kickPat[0],		hiWhinePat[0],	hiWhinePat[1]	]), 
		Ppar([	bassPat[2], 	loTonePat[0],	loTonePat[2],	hatPat[2], 	kickPat[0],						hiWhinePat[1]	]),
		drop[0],
								loTonePat[2]
	]);

	song.play;
)

Here is the final piece:

The full compilation can be downloaded from Waxen Wings for free and is highly recommended