SCGrains, SuperCollider UI Test

2 Granular synthesizers that use an audio file as source. These two can be crossfaded or bin-wiped with each other to produce nice evolving textures. The basis are two TGrains2 buffer granulator UGens by BhobUGens (included with the sc3-plugins package for SuperCollider). This was made some time ago to test building an user interface in SuperCollider.

Sound example:

Audio clip: Adobe Flash Player (version 9 or above) is required to play this audio clip. Download the latest version here. You also need to have JavaScript enabled in your browser.

// synth 1: Buffer granulator binwiped with an other buffer granulator, with GUI
(
b = Buffer.alloc(s, 44100);
c = Buffer.alloc(s, 2048, 1);
d = Buffer.alloc(s, 2048, 1);
e = Buffer.alloc(s, 44100);

a = SynthDef(\synthOne, {|out = 0, bufnum = 0, trate = 10, rate1 = 1.0, rate2 = 1.0, amount = 10, centerPos = 0, centerPos2 = 0, binWipe = 0.5, time = 1, ampLow = 0.8, ampHigh = 0.99, gain = 0.5,type = 0, dur = 2.0, dur2 = 2.0, pan = 1.0, fadeType = 0, crossFade, crossFade2, att = 0.1, att2 = 0.1, dec = 0.1, dec2 = 0.1, shapeType = 0, ctrlSpeed = 0, ctrlReach = 0, shapeType2 = 0, ctrlSpeed2 = 0, ctrlReach2 = 0|
	var clk, width, amp, granu, granu2, chainA, chainB, chainout, chain, sig, granuMix, prosOut, chainoutPan, shapeControl, shapeControl2;

	clk = Select.kr(type,
		[Dust.kr(amount), Impulse.kr(amount)]);
	amp = TRand.kr(ampLow, ampHigh, clk);

	shapeControl = Select.kr(shapeType,[
		SinOsc.kr(ctrlSpeed, ctrlReach * 0.5, ctrlReach),
		LFTri.kr(ctrlSpeed, ctrlReach * 0.5, ctrlReach),
		LFSaw.kr(ctrlSpeed, ctrlReach * 0.5, ctrlReach),
		WhiteNoise.kr(ctrlReach * 0.5, ctrlReach)]
	);
	shapeControl2 = Select.kr(shapeType2,[
		SinOsc.kr(ctrlSpeed2, ctrlReach2 * 0.5, ctrlReach2),
		LFTri.kr(ctrlSpeed2, ctrlReach2 * 0.5, ctrlReach2),
		LFSaw.kr(ctrlSpeed2, ctrlReach2 * 0.5, ctrlReach2),
		WhiteNoise.kr(ctrlReach2 * 0.5, ctrlReach)]
	);
	granu = TGrains2.ar(2, clk, b.bufnum, rate1, (centerPos / 44100) + shapeControl, (dur / 44100), WhiteNoise.kr(pan), amp, att, dec, 4);
	granu2 = TGrains2.ar(2, clk, e.bufnum, rate2, (centerPos2 / 44100) + shapeControl2, (dur2 / 44100), WhiteNoise.kr(pan), amp, att2, dec2, 4);
	granuMix = LinXFade2.ar(granu, granu2, crossFade);

		chainA = FFT(c.bufnum, granu);
		chainB = FFT(d.bufnum, granu2);
		chain = PV_RandWipe(chainA, chainB, binWipe, clk);
		chainout = IFFT(chain);
		chainoutPan =  Pan2.ar(chainout, WhiteNoise.kr(pan));
		prosOut = LinXFade2.ar(chainoutPan, granuMix, crossFade2);

	Out.ar(out, gain * prosOut);
}).memStore;
)

// GUI code
(
var w, a, rate1Knob, rate2Knob, centerSlid, center2Slid, grainAmount, attackBox, ampLowSlid, opendialog1, ampHighSlid, panSlid, gainSlid, titel1, trigtext, replaceBufbutton, replaceBufbutton2, typeSlider, fadetypeSlider,  soundView1, soundView2, introtext, fadething, balanceFadeSlid, fadextext, fadeytext, fadeinf, cs, attKnob, decKnob, att2Knob, dec2Knob, ctrlShapeButton, ctrlSpeedKnob, ctrlReachKnob, shapeText, ctrlShape2Button, ctrlSpeed2Knob, ctrlReach2Knob;
	a = Synth(\synthOne);
	w = SCWindow.new("Synth One",Rect(350, 100, 510, 810)).front;
	w.view.decorator = FlowLayout(w.view.bounds);

		w.view.decorator.nextLine;
		typeSlider = EZSlider(w, 140@24, "TrigType", ControlSpec(0, 1, 'lin', 1),
		{|ez| s.sendMsg(\n_set, 1000, \type, ez.value)}, 0);

		trigtext = StaticText(w, Rect(10, 10, 200, 20));
	 	trigtext.string = "0 = Dust, 1 = Impulse";

		w.view.decorator.nextLine;
	 	replaceBufbutton = Button(w, Rect(20, 20, 60, 20));
	 		replaceBufbutton.states = [["Audio 1"]];
	 		replaceBufbutton.action = {|state|
	 			if(state.value == 0, {
	 					CocoaDialog.getPaths({ |paths|
							paths.do({ |file|
							if (b.notNil, {b.free});
							b = Buffer.read(s, file.postln);

							f = SoundFile.new;
	 						f.openRead(b.path);
	 						soundView1.soundfile = f;
	 						soundView1.read(0, f.numFrames);
	 						soundView1.refresh;
	 						//how to re-calc "~j" -> ControlSpec for update "Reach" param. -> check objectSpec
							})

						})
				});
		};

	 	titel1 = StaticText(w, Rect(10, 10, 100, 20));
	 	titel1.string = "Granulator 1";

		introtext = StaticText(w, Rect(10, 10, 300, 20));
	 	introtext.string = "open file and make a selection in audio to start";

	 	w.view.decorator.nextLine;
	 	soundView1 = SCSoundFileView.new(w, Rect(20, 20, 500, 60));
	 	soundView1.action_({ arg view;
	 	var where;
	 	where = (view.selections[0]);
	 	where.postln;
	 	s.sendMsg(\n_set, 1000, \centerPos, where[0], \dur, where[1]);
	 	});

	 	w.view.decorator.nextLine;
	 	shapeText = StaticText(w, Rect(10, 10, 300, 20));
	 	shapeText.string = "Auto scrub buffer position with shape";
	 	w.view.decorator.nextLine;
	 	ctrlShapeButton = Button(w, Rect(20, 20, 60, 20))
	 		.states_([
	 			["Sine", Color.black, Color.rand],
	 			["Triangle", Color.black, Color.rand],
	 			["Saw", Color.black, Color.rand],
	 			["Random", Color.black, Color.rand]
	 		])
	 		.action_({ |state|
	 			if (state.value == 0,
	 				{ s.sendMsg(\n_set, 1000, \shapeType, 0)});
	 			if (state.value == 1,
	 				{ s.sendMsg(\n_set, 1000, \shapeType, 1)});
	 			if (state.value == 2,
	 				{ s.sendMsg(\n_set, 1000, \shapeType, 2)});
	 			if (state.value == 3,
	 				{ s.sendMsg(\n_set, 1000, \shapeType, 3)});
	 		});

		ctrlSpeedKnob = EZKnob(w, 181@32, "speed 1", ControlSpec(0.0, 1.0, 'lin', 0.001, 0.0, 'Hz'),
		{|ez| s.sendMsg(\n_set, 1000, \ctrlSpeed, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);
	 	ctrlShape2Button = Button(w, Rect(20, 20, 60, 20))
	 		.states_([
	 			["Sine", Color.black, Color.rand],
	 			["Triangle", Color.black, Color.rand],
	 			["Saw", Color.black, Color.rand],
	 			["Random", Color.black, Color.rand]
	 		])
	 		.action_({ |state|
	 			if (state.value == 0,
	 				{ s.sendMsg(\n_set, 1000, \shapeType2, 0)});
	 			if (state.value == 1,
	 				{ s.sendMsg(\n_set, 1000, \shapeType2, 1)});
	 			if (state.value == 2,
	 				{ s.sendMsg(\n_set, 1000, \shapeType2, 2)});
	 			if (state.value == 3,
	 				{ s.sendMsg(\n_set, 1000, \shapeType2, 3)});
	 		});

		ctrlSpeed2Knob = EZKnob(w, 181@32, "speed 2", ControlSpec(0.0, 1.0, 'lin', 0.001, 0.0, 'Hz'),
		{|ez| s.sendMsg(\n_set, 1000, \ctrlSpeed2, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		~j = [0.0, b.numFrames / 44100, 'lin', 0.01, 0.0, "sec"].asSpec; //is dit goed? (global variable) -> check serveroptions (architecture)
		w.view.decorator.nextLine;	//so reach doesn't update because the ControlSpec doesn't update ->

		//Server.local.options.blockSize

		ctrlReachKnob = EZKnob(w, 245@32, "reach 1", j,
		{|ez| s.sendMsg(\n_set, 1000, \ctrlReach, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

	 	ctrlReach2Knob = EZKnob(w, 245@32, "reach 2", ControlSpec(0.0, e.numFrames / 44100, 'lin', 0.01, 0.0, 'sec'),
		{|ez| s.sendMsg(\n_set, 1000, \ctrlReach2, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		w.view.decorator.nextLine;
	 	StaticText(w, Rect(10, 10, 500, 10));

	 	w.view.decorator.nextLine;
		rate1Knob = EZKnob(w, 245@32, "pitch 1", ControlSpec(0.1, 5.0, 'lin', 0.01, 1.0, 'rate'),
		{|ez| s.sendMsg(\n_set, 1000, \rate1, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		rate2Knob = EZKnob(w, 245@32, "pitch 2", ControlSpec(0.1, 5.0, 'lin', 0.01, 1.0, 'rate'),
		{|ez| s.sendMsg(\n_set, 1000, \rate2, ez.value)}, unitWidth:30, layout: \horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		w.view.decorator.nextLine;
		attKnob = EZKnob(w, 245@32, "attack 1", ControlSpec(0.01, 0.5, 'lin', 0.01, 0.1, 'sec'),
		{|ez| s.sendMsg(\n_set, 1000, \att, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		att2Knob = EZKnob(w, 245@32, "attack 2", ControlSpec(0.01, 0.5, 'lin', 0.01, 0.1, 'sec'),
		{|ez| s.sendMsg(\n_set, 1000, \att2, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		w.view.decorator.nextLine;
		decKnob = EZKnob(w, 245@32, "decay 1", ControlSpec(0.01, 0.5, 'lin', 0.01, 0.1, 'sec'),
		{|ez| s.sendMsg(\n_set, 1000, \dec, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

		dec2Knob = EZKnob(w, 245@32, "decay 2", ControlSpec(0.01, 0.5, 'lin', 0.01, 0.1, 'sec'),
		{|ez| s.sendMsg(\n_set, 1000, \dec2, ez.value)}, unitWidth:30, layout:\horz)
			.setColors(Color.grey, Color.white, Color.grey(0.7), Color.grey, Color.white, Color.yellow);

	 	w.view.decorator.nextLine;
	 	replaceBufbutton2 = Button(w, Rect(20, 20, 60, 20));
	 		replaceBufbutton2.states = [["Audio 2"]];
	 		replaceBufbutton2.action = {|state|
	 			if(state.value == 0, {
	 				CocoaDialog.getPaths({ |paths|
						paths.do({ |file|
						if (e.notNil, {e.free});
						e = Buffer.read(s, file.postln);

						g = SoundFile.new;
	 					g.openRead(e.path);
	 					soundView2.soundfile = g;
	 					soundView2.read(0, g.numFrames);
	 					soundView2.refresh;
						})
					})
				})
			};

		titel1 = StaticText(w, Rect(10, 10, 200, 20));
	 	titel1.string = "Granulator 2";

	 	w.view.decorator.nextLine;
	 	soundView2 = SCSoundFileView.new(w, Rect(20, 20, 500, 60));
	 	soundView2.action_({ arg view;
	 	var where2;

	 	where2 = (view.selections[0]);
	 	where2.postln;
	 	s.sendMsg(\n_set, 1000, \centerPos2, where2[0], \dur2, where2[1]);
	 	});

		titel1 = StaticText(w, Rect(10, 10, 200, 20));
	 	titel1.string = "Shared parameters";

		w.view.decorator.nextLine;
		grainAmount = EZSlider(w, 500@24, "Grains", ControlSpec(1, 100, 'lin', 1),
		{|ez| s.sendMsg(\n_set, 1000, \amount, ez.value)}, 10);

		w.view.decorator.nextLine;
		panSlid = EZSlider(w, 500@24, "spread", ControlSpec(0.0, 1.0, 'lin', 0.01),
		{|ez| s.sendMsg(\n_set, 1000, \pan, ez.value)}, 0.6);

		w.view.decorator.nextLine;
		ampLowSlid = EZSlider(w, 500@24, "min amp", ControlSpec(0.01, 1.0, 'lin', 0.01),
		{|ez| s.sendMsg(\n_set, 1000, \ampLow, ez.value)}, 0.3);

		w.view.decorator.nextLine;
		ampHighSlid = EZSlider(w, 500@24, "max amp", ControlSpec(0.01, 1.0, 'lin', 0.01),
		{|ez| s.sendMsg(\n_set, 1000, \ampHigh, ez.value)}, 0.5);

		w.view.decorator.nextLine;
		fadeinf = StaticText(w, Rect(10, 10, 200, 20));
	 	fadeinf.string = "Balance between Bin Wipe & XFade";

		w.view.decorator.nextLine;
		balanceFadeSlid = EZSlider(w, 500@24, "Y<->X", ControlSpec(-1.0, 1.0, 'lin', 0.01),
		{|ez| s.sendMsg(\n_set, 1000, \crossFade2, ez.value)}, 0.0);

	 	w.view.decorator.nextLine;
		fadething = Slider2D(w, Rect(0, 40, 240, 120), ControlSpec(0.0, 1.0, 'lin', 0.01))
				.y_(0.5)
				.x_(0.5)
				.knobColor_(Color.rand);
			cs = ControlSpec(-1.0, 1.0, 'lin', 0.0, 1.0);
				fadething.action_({|sl|
					a.set(\crossFade, cs.map(sl.x), \binWipe, sl.y);
				});

		fadeytext = StaticText(w, Rect(10, 10, 200, 20));
	 	fadeytext.string = "Y = Bin Wipe";

		fadextext = StaticText(w, Rect(10, 10, 200, 20));
	 	fadextext.string = "X = CrossFade";

		w.view.decorator.nextLine;
		gainSlid = EZSlider(w, 500@24, "GAIN", ControlSpec(0.0, 5.0, 'lin', 0.01),
		{|ez| s.sendMsg(\n_set, 1000, \gain, ez.value)}, 0.5);
		gainSlid.setColors(Color.red, Color.white);

	CmdPeriod.doOnce({w.close});
)