Some SuperCollider code for the bored – sinGrain

sinGrain

Granular synthesis with sinusoidal grains + a simple GUI.
Its a Synthdef using sinGrainB (a ugen from the SC3-plugins package) by Josh. You can set boundaries for random values for grain- amplitudes, durations, panning and frequencies. Frequency can be split, so only two frequencies are played instead of random frequencies in between the two values. Frequencies can be set using number boxes or a range slider. Grains can be triggered periodically (by choosing Impulse) or irregular (choosing Dust from the trigger menu), both use the spd slider for their speed.

// SinGrain with Interface

(
// grain envelope
z = Env([0,1,0], [1,1], \sine).asSignal(1024);
s.sendMsg(\b_alloc, b = s.bufferAllocator.alloc(1), 1024, 1, [\b_setn, b, 0, 1024] ++ z);
)

(
SynthDef(\sinGrain, {|out=0, envbuf, gate=1, atk=0.5, dec=0.2, sus=0.9, rel=0.5, ttype=0, tspeed=5, durMin=1.25, durMax=1.5, freqType=1, freqMin=220, freqMax=220, ampMin=0.8, ampMax=1, panMin=0, panMax=0, gain=0.65|
 var env, source, trigger, pan, dur, amp, freq;
 trigger = Select.kr(ttype, [Impulse.kr(tspeed), Dust.kr(tspeed)]);
 env = EnvGen.kr(Env.adsr(atk, dec, sus, rel), gate, doneAction:2);
 amp = TRand.kr(ampMin, ampMax, trigger);
 dur = TRand.kr(durMin, durMax, trigger);
 pan = TRand.kr(panMin, panMax, trigger);
 freq = Select.kr(freqType, [
 Demand.kr(trigger, 0, Drand([freqMin, freqMax], inf)),
 TRand.kr(freqMin, freqMax, trigger)
 ]);
 source = SinGrainB.ar(trigger, dur, freq, envbuf, amp);
 source = Pan2.ar(source, pan, gain);
 Out.ar(out, source * env);
}).send(s);
)

/*
s.sendMsg(\s_new, \sinGrain, x=s.nextNodeID, 0, 0, \out, 0);
s.sendMsg(\n_set, \panMin, -1, \panMax, 1);
s.sendMsg(\n_set, x, \gate, 0);
*/

/**************************************** GUI ****************************************/
(
var synthNode = 3000;
w = Window("sinGrain", 370@320, false, true);
w.front;

Button(w, Rect(0, 0, 370, 40))
.states_([
 ["push", Color.rand, Color.rand],
 ["push", Color.rand, Color.rand]
])
.action_({|button|
 if(button.value == 1, {
 s.sendMsg(\s_new, \sinGrain, synthNode, 0, 0, \gate, 1)
 },{
 s.sendMsg(\n_set, synthNode, \gate, 0)
 }
 );
});

//Trigger type
g = EZPopUpMenu(w, Rect(-22, 50, 180, 20), "trigger");
g.addItem(\Impulse, {|a| (s.sendMsg(\n_set, synthNode, \ttype, 0))});
g.addItem(\Dust, {|a| (s.sendMsg(\n_set, synthNode, \ttype, 1))});
g.value = 0;

// Trigger speed
EZSlider(w, Rect(-22, 80, 365, 20), "spd", [0, 30, \lin, 0.1, 5].asSpec, {|slider| s.sendMsg(\n_set, synthNode, \tspeed, slider.value)}, nil, true, 60, 0, 0, 20, \horz);

StaticText(w, Rect(10, 110, 30, 20)).string_("amp");
//Amplitude Range Slider
RangeSlider(w, Rect(40, 110, 300, 20))
.lo_(0.8)
.hi_(1.0)
.action_({|amp| s.sendMsg(\n_set, synthNode, \ampMin, amp.lo, \ampMax, amp.hi)});

StaticText(w, Rect(10, 140, 30, 20)).string_("dur");
//Duration Range Slider
RangeSlider(w, Rect(40, 140, 300, 20))
.lo_(0.4)
.hi_(0.5)
.action_({|dur| s.sendMsg(\n_set, synthNode, \durMin, [0, 3.0, \lin, 0.01].asSpec.map(dur.lo), \durMax, [0, 3.0, \lin, 0.01].asSpec.map(dur.hi))});

StaticText(w, Rect(10, 170, 30, 20)).string_("pan");
//Panning Range Slider
RangeSlider(w, Rect(40, 170, 300, 20))
.lo_(0.5)
.hi_(0.5)
.action_({|pan| s.sendMsg(\n_set, synthNode, \panMin, [-1, 1.0, \lin, 0.01].asSpec.map(pan.lo), \panMax, [-1, 1.0, \lin, 0.01, 0].asSpec.map(pan.hi))});

//Trigger type
h = EZPopUpMenu(w, Rect(-13, 200, 180, 20), "split freq");
h.addItem(\split, {|a| (s.sendMsg(\n_set, synthNode, \freqType, 0))});
h.addItem(\random, {|a| (s.sendMsg(\n_set, synthNode, \freqType, 1))});
h.value = 1;

// FREQUENCY IS SET BY TWO NUMBER BOXES OR RANGE SLIDER

//Freq Min Max boxes
EZNumber(w, Rect(5, 230, 130, 20), "freq min", [20, 20000, \lin, 1, 220, "Hz"].asSpec, {|fmin| s.sendMsg(\n_set, 3000, \freqMin, fmin.value)});
EZNumber(w, Rect(150, 230, 130, 20), "freq max", [20, 20000, \lin, 1, 220, "Hz"].asSpec, {|fmax| s.sendMsg(\n_set, 3000, \freqMax, fmax.value)}, labelHeight:20);

StaticText(w, Rect(10, 260, 30, 20)).string_("freq");
//Frequency Range Slider
RangeSlider(w, Rect(40, 260, 300, 20))
.lo_(0.5)
.hi_(0.5)
.action_({|freq| s.sendMsg(\n_set, synthNode, \freqMin, [20, 20000, \exp, 1, 220].asSpec.map(freq.lo), \freqMax, [20, 20000, \exp, 1, 220].asSpec.map(freq.hi))});

// Trigger speed
EZSlider(w, Rect(-22, 290, 365, 20), "gain", [0, 1, \lin, 0.01, 0.65].asSpec, {|volume| s.sendMsg(\n_set, synthNode, \gain, volume.value)}, nil, true, 60, 0, 0, 20, \horz);

CmdPeriod.doOnce({w.close});
)
<pre>

SCGrains, SuperCollider UI Test

SCGrains

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});
)