Designing Sound in SuperCollider/Cars

Fig 45.3: A "toy" engine edit

Instead of using a toggle object to simulate the break, the same functionality is implemented by moving the mouse cursor into the right or left half of the screen.

(
{
	var toy, toggle, noise;
	
	toggle = MouseX.kr(0,1).round(1);
	
	toy = BPF.ar(WhiteNoise.ar, 9, 15.reciprocal); 
	toy = (toggle * toy);
	toy = (toy + (SinOsc.ar(9) * K2A.ar(Select.kr(toggle, [1,0])))) * 600;
	toy = Clip.ar(toy, 0, 1);
	toy = (toy - OnePole.ar(toy, exp(-2pi * (10 * SampleDur.ir))));
	toy = OnePole.ar(toy, exp(-2pi * (30 * SampleDur.ir)));
	
	noise = WhiteNoise.ar;
	noise = (noise - OnePole.ar(noise, exp(-2pi * (1000 * SampleDur.ir))));
	noise = BPF.ar(noise, 590, 4.reciprocal);
	
	toy = toy * noise; 
	
	toy = BPF.ar(toy, [470, 780, 1024], [8, 9, 10].reciprocal).sum;
	toy = (toy - OnePole.ar(toy, exp(-2pi * (100 * SampleDur.ir))));
	toy = (toy * 2).dup;
	
}.play;
)

Fig 45.4/45.5: A four cylinder engine with slugging speed edit

(
{
	var jitterEngine, noise, bufferA, bufferB, fourstroke, engineSpeed;
	
	bufferA = LocalBuf(44100, 1); 
	bufferB = LocalBuf(44100, 1); 
		
	engineSpeed = MouseX.kr(0,1);
	
	noise = WhiteNoise.ar;
	noise = OnePole.ar(noise, exp(-2pi * (20 * SampleDur.ir)));
	noise = OnePole.ar(noise, exp(-2pi * (20 * SampleDur.ir)));
	noise = DelTapWr.ar([bufferA, bufferB], [noise * 0.5, noise * 10]);
		
	fourstroke = DelTapRd.ar(bufferA, noise[0], [5, 10, 15, 20]/1000); 
	fourstroke = 
			LFSaw.ar(OnePole.ar((K2A.ar(engineSpeed) * 40), exp(-2pi * (0.8 * SampleDur.ir))), 1, 0.5, 0.5)
		 	 + fourstroke
		 	 - [0.75, 0.5, 0.25, 0];
	fourstroke = (fourstroke * 2pi).cos;
	fourstroke.scope;
	
	fourstroke = 
			fourstroke 
			* (DelTapRd.ar(bufferB, noise[1], [5, 10, 15, 20]/1000) + ((1 - engineSpeed) * 15 + 7));
	fourstroke = 1 / ((fourstroke * fourstroke) + 1);
	fourstroke = fourstroke.sum!2 * 0.25;
	
}.play;
)

Fig 45.8: Advanced Engine edit

Advanced engine with multiple transmission paths and warping non-linear waveguide. Contains the subpatches from fig 45.5, 45.6 and 45.7. At the end there is also an example how to control some parameters via a MIDI controller.

(
e = SynthDef(\engine, {
	
	| // arguments range: 0.0 - 1.0 	
	mixCylinders   = 0.8,
	mixParabolic   = 0.9,
	engineSpeed    = 0, 
	parabolaDelay  = 0.15,
	warpDelay      = 0.4,
	waveguideWarp  = 0.67,
	wguideFeedback = 0.35,
	wguideLength1  = 0.2,
	wguideLength2  = 0.3,
	wguideWidth1   = 0.5,
	wguideWidth2   = 0.7
	|

	// To be able to send arrays as arguments you have to declare them as variables and
	// use NamedControl.kr. Take also a look at the MIDI example at the bottom how to address them. 
	var transDelay = NamedControl.kr(\transDelay, [0.2, 0.3, 0.45]);
	var overtonePhase = NamedControl.kr(\overtonePhase, [0.25, 0.35, 0.5]);
	var overtoneFreq = NamedControl.kr(\overtoneFreq, [0.3, 0.47, 0.38]);
	var overtoneAmp = NamedControl.kr(\overtoneAmp, [0.1, 0.2, 0.2]);
	
	var noise, bufferA, bufferB, bufferTd, fourstroke, phasor, td, parabola, fm1, preFM1, 
	fm2, preFM2, overtone, overtoneDrive, e1b, e2a, e2b, e1a, spacewarp, engine;
		
	engineSpeed = MouseX.kr(0,1);
	

	bufferA = LocalBuf(44100, 1); 
	bufferB = LocalBuf(44100, 1); 
	bufferTd = LocalBuf(44100, 1); 

		
	noise = WhiteNoise.ar;
	noise = OnePole.ar(noise, exp(-2pi * (20 * SampleDur.ir)));
	noise = OnePole.ar(noise, exp(-2pi * (20 * SampleDur.ir)));
	noise = (DelTapWr.ar([bufferA, bufferB], [noise * 0.5, noise * 30]));
	
	phasor = LFSaw.ar(
		OnePole.ar(K2A.ar(engineSpeed) * 30, exp(-2pi * (0.8 * SampleDur.ir))), 
		1, 0.5, 0.5);
	td = DelTapWr.ar(bufferTd, phasor);
		
	fourstroke = DelTapRd.ar(bufferA, noise[0], [5, 10, 15, 20]/1000, 4); 
	fourstroke = phasor + fourstroke - [0.75, 0.5, 0.25, 0];
	fourstroke = (fourstroke * 2pi).cos;
	fourstroke = fourstroke * (DelTapRd.ar(bufferB, noise[1], [5, 10, 15, 20]/1000, 4) + ((1 - engineSpeed) * 15 + 7));
	fourstroke = 1 / ((fourstroke * fourstroke) + 1);
	fourstroke = fourstroke.sum * mixCylinders;
	fourstroke = fourstroke - OnePole.ar(fourstroke, exp(-2pi * (4 * SampleDur.ir)));
	

	parabola = DelTapRd.ar(bufferTd, td, (parabolaDelay * 100)/1000, 1) - 0.5;
	parabola = parabola * parabola * (-4) + 1 * 3 * mixParabolic;
	
	
	preFM1 = DelTapRd.ar(bufferTd, td, (warpDelay * 100)/1000, 1);
	preFM1 = (preFM1 * 2pi).cos;
	preFM2 = K2A.ar(engineSpeed * waveguideWarp);
	preFM2 = OnePole.ar(preFM2, exp(-2pi * (0.2 * SampleDur.ir)));
	fm1 = (1 - preFM1) * preFM2 + 0.5;
	fm2 = (preFM2 * preFM1) + 0.5;
	
	
	overtoneDrive  = overtoneDrive!3;
	overtone = overtone!3;
	
	3.do{|i|
		
		overtoneDrive[i] = DelTapRd.ar(bufferTd, td, (transDelay[i]*100)/1000) * (0.5**(i+1)*32);
		overtoneDrive[i] = Wrap.ar(overtoneDrive[i]);
		
		overtone[i] = overtoneDrive[i].max(overtonePhase[i]) - overtonePhase[i];
		overtone[i] = overtone[i] * (1 - overtonePhase[i]).reciprocal;
		overtone[i] = overtone[i] * ((overtoneFreq[i] * 12) * overtonePhase[i]);
		overtone[i] = Wrap.ar(overtone[i]) - 0.5;
		overtone[i] = (overtone[i] * overtone[i]) * (-4) + 1 * 0.5;
		overtone[i] = (overtone[i] * (1 - overtoneDrive[i])) * (overtoneAmp[i] * 12);
	};
	

	# e1b, e2b, e2a, e1a = DelayC.ar(
		in: InFeedback.ar(bus:(10..13)), 
		maxdelaytime: 1,
		delaytime: ((([wguideLength1,wguideWidth1,wguideLength2,wguideWidth2] * 40) 
			* [fm1,fm1,fm2,fm1])/1000)
	);
	
	OffsetOut.ar(11, e1b + overtone[1]);
	
	e2b = e2b + overtone[2];
	OffsetOut.ar(13, e2b);	
	
	e2a = e2a + overtone[0];
	OffsetOut.ar(10, e2a);
		
	OffsetOut.ar(12, e1a * wguideFeedback + (parabola - OnePole.ar(parabola, exp(-2pi * (30 * SampleDur.ir)))));
	
	spacewarp = e1b + e2b + e2a + e1a;
	spacewarp = spacewarp - OnePole.ar(spacewarp, exp(-2pi * (200 * SampleDur.ir)));
	spacewarp = spacewarp - OnePole.ar(spacewarp, exp(-2pi * (200 * SampleDur.ir)));
	
	engine = (spacewarp + fourstroke)!2 * 0.5;
	
	Out.ar(0, engine);

}).play;
)

For testing so many different parameters at once, a device with multiple controllers is your best friend.

MIDIIn.connectAll;
(
	var transFreq = Array.newClear(3);	
	
	MIDIFunc.cc({ |val, num|
		switch( num, 
		1,	{e.set(\wguideFeedback, (val/128).range(0,1).postln)},
		2,	{e.set(\mixParabolic, (val/128).range(0,1).postln)},
		3,	{e.setn(\overtoneFreq, transFreq.put(0, (val/128).range(0,1)).postln)},
		4,	{e.setn(\overtoneFreq, transFreq.put(1, (val/128).range(0,1)).postln)},
		5,	{e.setn(\overtoneFreq, transFreq.put(2, (val/128).range(0,1)).postln)},
		6,	{e.set(\parabolicDelay, (val/128).range(0,1).postln)},
		7,	{e.set(\warpDelay, (val/128).range(0,1).postln)},
		8,	{e.set(\waveguideWarp, (val/128).range(0,1).postln)},
		)	
	});
	
)