• jon@schemawound.com
Supercollider
SIGNALVOID 3: The Piano

SIGNALVOID 3: The Piano

SIGNALVOID is a noise compilation. Participants were asked to create up to three tracks, each of exactly one minute in length, with no gaps of silence at the beginning or end. Download the compilation here: http://archive.org/details/SignalvoidMp3

This piece was my third submission for SIGNALVOID.  After “Burn It To Disc And Then Burn The Disc” and “The Same Color As Your Skin” I was not sure where to go with my final submission to the compilation.  While digging through old code for inspiration I found this piece.  I was designing it to be used with a GUI and gave up before it was completed.  It was untouched for several months before I decided to revive it for the compilation.  I made some minor changes to the code to clean up the sound and remove the GUI and used it pretty much as is.  This is the piece that inspired my Subsonic Issues post so you will want to add a high pass filter if you decide to run this code yourself.

(
	SynthDef(ThePiano, {
		|
			sampleHoldFreq = 1,			saw0Freq = 0.1,			saw0DepthMin = 1,		saw0DepthMax = 1000,
			saw1FreqLeft = 10,			saw1FreqRight = 10.92, 
			saw2FreqLeft = 9, 			saw2FreqRight = 11.59,
			saw5FreqLeft = 0.1,			saw5FreqRight = 0.125,	saw5DepthMin = 1,		saw5DepthMax = 60
			i_comb0MaxDelay = 1,		comb0Delay = 0.001,		comb0Decay = 0.1,
			i_comb1MaxDelay = 1,		comb1Delay = 0.0125,	comb1Decay = 0.1,
			verb0Mix		= 1.0,		verb0Room = 0.7,
			verb1Mix		= 1.0,		verb1Room = 1.0,
			amp = 1
		|

		//Local Vars
		var sampleAndHold;
		var saw = Array.new;
		var comb = Array.new;
		var verb = Array.new;
		var comp;
		
		//Saws
		/* -----------
			Saw Index:
			0: Mod for Saw 3 (Sample and Hold mod)
			1: Dual Channel Saw
			2: Dual Channel Saw
			3: Saw with freq modified by a S&H
			4: 1 * 2 * 3
			5: Mod for saw 6
			6: 5 * 4, used for reverb
		-------------*/
		sampleAndHold = Latch.ar(WhiteNoise.ar, Impulse.ar(sampleHoldFreq));
		saw = saw.add(SinOsc.ar(saw0Freq).range(saw0DepthMin, saw0DepthMax));
		saw = saw.add(LFSaw.ar([saw1FreqLeft, saw1FreqRight]));
		saw = saw.add(LFSaw.ar([saw2FreqLeft, saw2FreqRight]));
		saw = saw.add(LFSaw.ar(sampleAndHold * saw[0]));
		saw = saw.add(saw[1] * saw[2] * saw[3]);
		saw = saw.add(LFSaw.kr([saw5FreqLeft, saw5FreqRight]).range(saw5DepthMin, saw5DepthMax));
		saw = saw.add(LFSaw.ar(saw [5] * saw[4]));
		
		//Combs
		comb = comb.add(CombC.ar(saw[4],	i_comb0MaxDelay, 	comb0Delay, 	comb0Decay));
		comb = comb.add(CombC.ar(comb[0],	i_comb1MaxDelay, 	comb1Delay, 	comb1Decay));
		
		//Verbs
		verb = verb.add(FreeVerb.ar(comb[1], 			verb0Mix,	verb0Room));
		verb = verb.add(FreeVerb.ar(verb[0] * saw[6],	verb1Mix,	verb1Room));
		
		//Out
		Out.ar(0, verb[1] * (amp * 0.2));
	}).add;
)

(
	x = Synth(ThePiano, [
		saw0ModFreq, 		0.1,	saw0ModDepthMin,	1,		saw0ModDepthMax,	1000,	
		saw1FreqLeft,		10,		saw1FreqRight,		10.92,
		saw2FreqLeft,		9,		saw2FreqRight,		11.59,
		saw5FreqLeft,		0.1,	saw5FreqRight,		0.125,	saw5DepthMin,		1,		saw5DepthMax,		60,
		i_comb0MaxDelay,	0.001,	comb0Delay,		0.001,	comb0Decay,		0.1,
		i_comb1MaxDelay,	0.0125,	comb1Delay,		0.0125,	comb1Decay,		0.1,
		verb0Mix,			1.0,	verb0Room,			0.7,	
		verb1Mix,			1.0,	verb1Room,			1.0,
		amp,				1
	]);
)

For those curious of how the GUI version worked the code is below. If I remember properly a lot of the knobs are far too subtle to make a difference in the sound.

(
	SynthDef(ThePiano, {
		|
			sampleHoldFreq = 1,			saw0Freq = 0.1,			saw0DepthMin = 1,		saw0DepthMax = 1000,
			saw1FreqLeft = 10,			saw1FreqRight = 10.92, 
			saw2FreqLeft = 9, 			saw2FreqRight = 11.59,
			saw5FreqLeft = 0.1,			saw5FreqRight = 0.125,	saw5DepthMin = 1,		saw5DepthMax = 60
			i_comb0MaxDelay = 1,		comb0Delay = 0.001,		comb0Decay = 0.1,
			i_comb1MaxDelay = 1,		comb1Delay = 0.0125,	comb1Decay = 0.1,
			verb0Mix		= 1.0,		verb0Room = 0.7,
			verb1Mix		= 1.0,		verb1Room = 1.0,
			amp = 1
		|

		//Local Vars
		var sampleAndHold;
		var saw = Array.new;
		var comb = Array.new;
		var verb = Array.new;
		var comp;
		
		//Saws
		/* -----------
			Saw Index:
			0: Mod for Saw 3 (Sample and Hold mod)
			1: Dual Channel Saw
			2: Dual Channel Saw
			3: Saw with freq modified by a S&H
			4: 1 * 2 * 3
			5: Mod for saw 6
			6: 5 * 4, used for reverb
		-------------*/
		sampleAndHold = Latch.ar(WhiteNoise.ar, Impulse.ar(sampleHoldFreq));
		saw = saw.add(SinOsc.ar(saw0Freq).range(saw0DepthMin, saw0DepthMax));
		saw = saw.add(LFSaw.ar([saw1FreqLeft, saw1FreqRight]));
		saw = saw.add(LFSaw.ar([saw2FreqLeft, saw2FreqRight]));
		saw = saw.add(LFSaw.ar(sampleAndHold * saw[0]));
		saw = saw.add(saw[1] * saw[2] * saw[3]);
		saw = saw.add(LFSaw.kr([saw5FreqLeft, saw5FreqRight]).range(saw5DepthMin, saw5DepthMax));
		saw = saw.add(LFSaw.ar(saw [5] * saw[4]));
		
		//Combs
		comb = comb.add(CombC.ar(saw[4],	i_comb0MaxDelay, 	comb0Delay, 	comb0Decay));
		comb = comb.add(CombC.ar(comb[0],	i_comb1MaxDelay, 	comb1Delay, 	comb1Decay));
		
		//Verbs
		verb = verb.add(FreeVerb.ar(comb[1], 			verb0Mix,	verb0Room));
		verb = verb.add(FreeVerb.ar(verb[0] * saw[6],	verb1Mix,	verb1Room));
		
		//Out
		Out.ar(0, verb[1] * (amp * 0.2));
	}).add;
)

/*
(
	x = Synth(ThePiano, [
		saw0ModFreq, 		0.1,	saw0ModDepthMin,	1,		saw0ModDepthMax,	1000,	
		saw1FreqLeft,		10,		saw1FreqRight,		10.92,
		saw2FreqLeft,		9,		saw2FreqRight,		11.59,
		saw5FreqLeft,		0.1,	saw5FreqRight,		0.125,	saw5DepthMin,		1,		saw5DepthMax,		60,
		i_comb0MaxDelay,	0.001,	comb0Delay,		0.001,	comb0Decay,		0.1,
		i_comb1MaxDelay,	0.0125,	comb1Delay,		0.0125,	comb1Decay,		0.1,
		verb0Mix,			1.0,	verb0Room,			0.7,	
		verb1Mix,			1.0,	verb1Room,			1.0,
		amp,				1
	]);
)
*/

(
	//----------ThePiano GUI----------
	//Global Variables
	var layout;
	var knobSize = 60@80;
	var knobResolution = 0.0001;
	var buttonSize = 20@20;
	var titleFont = Font( "Times-Bold", 25 );
	var titleSize = 120@25;

	//Knob Variables
	var sampleHoldView, sampleHoldFreqKnob, saw0FreqKnob, saw0DepthMinKnob, saw0DepthMaxKnob;
	var saw1View, saw1FreqLKnob, saw1FreqRKnob;
	var saw2View, saw2FreqLKnob, saw2FreqRKnob;
	var saw5View, saw5FreqLKnob, saw5FreqRKnob, saw5DepthMinKnob, saw5DepthMaxKnob;
	var comb0View, comb0DelayKnob, comb0DecayKnob;
	var comb1View, comb1DelayKnob, comb1DecayKnob;
	var verbView, verbMixKnob, verb0RoomKnob, verb1RoomKnob;
	var masterView, ampKnob;

	//Start Synth
	~thePiano = Synth(ThePiano);

	//Window Setup
	~thePianoWindow = Window( "The Piano", Rect( 128, 230, 820, 260)); 
	~thePianoWindow.view.decorator = layout = FlowLayout( ~thePianoWindow.view.bounds ); 

	//Sample and Hold Knobs
	sampleHoldView = CompositeView(~thePianoWindow, 260@120);
	sampleHoldView.decorator = FlowLayout( sampleHoldView.bounds );
	StaticText(sampleHoldView, titleSize).string_("Sample and Hold").font = titleFont;	sampleHoldView.decorator.nextLine;
	sampleHoldFreqKnob = EZKnob(sampleHoldView, knobSize, "ImpulseFreq",	ControlSpec(0.01, 1000, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(sampleHoldFreq, inValue.value);}, layout:vert2);
	saw0FreqKnob = EZKnob(sampleHoldView, knobSize, "LfoFreq",	ControlSpec(0.0001, 5000, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw0ModFreq, inValue.value);}, layout:vert2);
	saw0DepthMinKnob = EZKnob(sampleHoldView, knobSize, "DepthMin",	ControlSpec(1, 5000, lin, knobResolution);, 
		action: {|inValue| 
				~thePiano.set(saw0DepthMin, inValue.value);
				if((inValue.value > saw0DepthMaxKnob.value), //Make sure min and max are tied together
					{saw0DepthMaxKnob.value = inValue.value; saw0DepthMaxKnob.doAction;}
				)
		}, layout:vert2);
	saw0DepthMaxKnob = EZKnob(sampleHoldView, knobSize, "DepthMax",	ControlSpec(1, 5000, lin, knobResolution);, 
		action: {|inValue| 
				~thePiano.set(saw0DepthMax, inValue.value);
				if((inValue.value < saw0DepthMinKnob.value), //Make sure min and max are tied together
					{saw0DepthMinKnob.value = inValue.value; saw0DepthMinKnob.doAction;}
				)
		}, layout:vert2);

	//Saw1 Knobs
	saw1View = CompositeView(~thePianoWindow, 140@120);
	saw1View.decorator = FlowLayout( saw1View.bounds );
	StaticText(saw1View, titleSize).string_("Saw 1").font = titleFont;	saw1View.decorator.nextLine;
	saw1FreqLKnob = EZKnob(saw1View, knobSize, "Freq_L",	ControlSpec(0.0001, 100, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw1FreqLeft, inValue.value);}, layout:vert2);
	saw1FreqRKnob = EZKnob(saw1View, knobSize, "Freq_R",	ControlSpec(0.0001, 100, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw1FreqRight, inValue.value);}, layout:vert2);

	//Saw2 Knobs
	saw2View = CompositeView(~thePianoWindow, 140@120);
	saw2View.decorator = FlowLayout( saw2View.bounds );
	StaticText(saw2View, titleSize).string_("Saw 2").font = titleFont;	saw2View.decorator.nextLine;
	saw2FreqLKnob = EZKnob(saw2View, knobSize, "Freq_L",	ControlSpec(0.0001, 100, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw2FreqLeft, inValue.value);}, layout:vert2);
	saw2FreqRKnob = EZKnob(saw2View, knobSize, "Freq_R",	ControlSpec(0.0001, 100, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw2FreqRight, inValue.value);}, layout:vert2);

	//Saw5 Knobs
	saw5View = CompositeView(~thePianoWindow, 260@120);
	saw5View.decorator = FlowLayout( saw5View.bounds );
	StaticText(saw5View, titleSize).string_("Saw 5").font = titleFont;	saw5View.decorator.nextLine;
	saw5FreqLKnob = EZKnob(saw5View, knobSize, "Freq_L",	ControlSpec(0.0001, 100, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw5FreqLeft, inValue.value);}, layout:vert2);
	saw5FreqRKnob = EZKnob(saw5View, knobSize, "Freq_R",	ControlSpec(0.0001, 100, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(saw5FreqRight, inValue.value);}, layout:vert2);
	saw5DepthMinKnob = EZKnob(saw5View, knobSize, "DepthMin",	ControlSpec(1, 1000, lin, knobResolution);, 
		action: {|inValue| 
				~thePiano.set(saw5DepthMin, inValue.value);
				if((inValue.value > saw5DepthMaxKnob.value), //Make sure min and max are tied together
					{saw5DepthMaxKnob.value = inValue.value; saw5DepthMaxKnob.doAction;}
				)
		}, layout:vert2);
	saw5DepthMaxKnob = EZKnob(saw5View, knobSize, "DepthMax",	ControlSpec(1, 1000, lin, knobResolution);, 
		action: {|inValue| 
				~thePiano.set(saw5DepthMax, inValue.value);
				if((inValue.value < saw5DepthMinKnob.value), //Make sure min and max are tied together
					{saw5DepthMinKnob.value = inValue.value; saw5DepthMinKnob.doAction;}
				)
		}, layout:vert2);

	//Comb0 Knobs
	comb0View = CompositeView(~thePianoWindow, 140@120);
	comb0View.decorator = FlowLayout( comb0View.bounds );
	StaticText(comb0View, titleSize).string_("Comb 0").font = titleFont;	comb0View.decorator.nextLine;
	comb0DelayKnob = EZKnob(comb0View, knobSize, "Delay",	ControlSpec(0.0001, 1, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(comb0Delay, inValue.value);}, layout:vert2);
	comb0DecayKnob = EZKnob(comb0View, knobSize, "Decay",	ControlSpec(0.0001, 10, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(comb0Decay, inValue.value);}, layout:vert2);

	//Comb1 Knobs
	comb1View = CompositeView(~thePianoWindow, 140@120);
	comb1View.decorator = FlowLayout( comb1View.bounds );
	StaticText(comb1View, titleSize).string_("Comb 1").font = titleFont;	comb1View.decorator.nextLine;
	comb1DelayKnob = EZKnob(comb1View, knobSize, "Delay",	ControlSpec(0.0001, 1, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(comb1Delay, inValue.value);}, layout:vert2);
	comb1DecayKnob = EZKnob(comb1View, knobSize, "Decay",	ControlSpec(0.0001, 10, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(comb1Decay, inValue.value);}, layout:vert2);

	//Verb Knobs
	verbView = CompositeView(~thePianoWindow, 200@120);
	verbView.decorator = FlowLayout( verbView.bounds );
	StaticText(verbView, titleSize).string_("Verb").font = titleFont;	verbView.decorator.nextLine;
	verbMixKnob = EZKnob(verbView, knobSize, "Mix",	ControlSpec(0, 1, lin, knobResolution);, 
		action: {|inValue| 
			~thePiano.set(verb0Mix, inValue.value);
			~thePiano.set(verb1Mix, inValue.value);
		}, layout:vert2);
	verb0RoomKnob = EZKnob(verbView, knobSize, "Room 0",	ControlSpec(0, 1, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(verb0Room, inValue.value);}, layout:vert2);
	verb1RoomKnob = EZKnob(verbView, knobSize, "Room 1",	ControlSpec(0, 1, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(verb1Room, inValue.value);}, layout:vert2);

	//Master Knobs
	masterView = CompositeView(~thePianoWindow, 140@120);
	masterView.decorator = FlowLayout( masterView.bounds );
	StaticText(masterView, titleSize).string_("Master").font = titleFont;	masterView.decorator.nextLine;
	ampKnob = EZKnob(masterView, knobSize, "Amp",	ControlSpec(0, 1, lin, knobResolution);, 
		action: {|inValue| ~thePiano.set(amp, inValue.value);}, layout:vert2);

	//Show the window
	~thePianoWindow.front;

	//Add Functions
	~thePianoWindow.onClose = {~thePiano.free;};

	//Set all knobs equal to current synth values, defaults sound be set by synthdef, not GUI
	~thePiano.get(sampleHoldFreq,	{|val| AppClock.sched(0.0, {sampleHoldFreqKnob.value = val.value})});
	~thePiano.get(saw0Freq,		{|val| AppClock.sched(0.0, {saw0FreqKnob.value = val.value})});
	~thePiano.get(saw0DepthMin,	{|val| AppClock.sched(0.0, {saw0DepthMinKnob.value = val.value})});
	~thePiano.get(saw0DepthMax,	{|val| AppClock.sched(0.0, {saw0DepthMaxKnob.value = val.value})});
	~thePiano.get(saw1FreqLeft,	{|val| AppClock.sched(0.0, {saw1FreqLKnob.value = val.value})});
	~thePiano.get(saw1FreqRight,	{|val| AppClock.sched(0.0, {saw1FreqRKnob.value = val.value})});
	~thePiano.get(saw2FreqLeft,	{|val| AppClock.sched(0.0, {saw2FreqLKnob.value = val.value})});
	~thePiano.get(saw2FreqRight,	{|val| AppClock.sched(0.0, {saw2FreqRKnob.value = val.value})});
	~thePiano.get(saw5FreqLeft,	{|val| AppClock.sched(0.0, {saw5FreqLKnob.value = val.value})});
	~thePiano.get(saw5FreqRight,	{|val| AppClock.sched(0.0, {saw5FreqRKnob.value = val.value})});
	~thePiano.get(saw5DepthMin,	{|val| AppClock.sched(0.0, {saw5DepthMinKnob.value = val.value})});
	~thePiano.get(saw5DepthMax,	{|val| AppClock.sched(0.0, {saw5DepthMaxKnob.value = val.value})});
	~thePiano.get(comb0Delay,		{|val| AppClock.sched(0.0, {comb0DelayKnob.value = val.value})});
	~thePiano.get(comb0Decay,		{|val| AppClock.sched(0.0, {comb0DecayKnob.value = val.value})});
	~thePiano.get(comb1Delay,		{|val| AppClock.sched(0.0, {comb1DelayKnob.value = val.value})});
	~thePiano.get(comb1Decay,		{|val| AppClock.sched(0.0, {comb1DecayKnob.value = val.value})});
	~thePiano.get(verb0Mix,		{|val| AppClock.sched(0.0, {verbMixKnob.value = val.value})});
	~thePiano.get(verb0Room,		{|val| AppClock.sched(0.0, {verb0RoomKnob.value = val.value})});
	~thePiano.get(verb1Room,		{|val| AppClock.sched(0.0, {verb1RoomKnob.value = val.value})});
	~thePiano.get(amp,				{|val| AppClock.sched(0.0, {ampKnob.value = val.value})});
	
	//Kill the gui if someone kills the sound
	CmdPeriod.doOnce({
		~thePianoWindow.close;
	});
)