Skip to content

Instantly share code, notes, and snippets.

@madskjeldgaard
Last active December 30, 2022 02:39
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save madskjeldgaard/fefd3e62360d3bafe59eb67d198ce484 to your computer and use it in GitHub Desktop.
Save madskjeldgaard/fefd3e62360d3bafe59eb67d198ce484 to your computer and use it in GitHub Desktop.
Sketch for a supercollider based cloud generator
/*
Sketch for a cloud generator
By Mads Kjeldgaard, 2020
*/
(
~numChannels = 2;
s.options.numOutputBusChannels_(~numChannels);
~filePath = "~/lee.flac";
s.waitForBoot{
fork{
// b = Buffer.read(s, "~/testsound/harmonica1.wav".asAbsolutePath).normalize;
b = Buffer.read(s, ~filePath.asAbsolutePath).normalize;
s.sync; "Done loading buffer".postln;
}
}
)
(
Ndef(\cloud, {|gdur=0.1, buffer, density=1.0, rate=0.95, shape=0.5, ratespread=0.0, pos=0.25, posspread=0.0, rand=1.0, overlap=0.0, amp=0.5|
var cloud;
var numGrains=4;
var numBufChans=2;
// Normalize params
density = density.linexp(0.0,1.0,0.0001,100.0);
rate = rate.linexp(0.0,1.0,0.01,10.0);
cloud = Array.fill(numGrains, {|gNum|
var coef=gNum+1/numGrains;
// Add tiny difference to each grain generator
var weight = Rand(0.9999,1.0);
var finalgdur = gdur * weight * overlap.linlin(0.0,1.0,1.0,4.0);
// Deterministic
var imp = Impulse.ar(density, phase: coef);
// Random impulses
var dust = Dust2.ar(density);
// Crossfade between them
var trig = XFade2.ar(imp, dust, rand.linlin(0.0,1.0,-1.0,1.0));
// Grain envelope
// Soft envelope
var sineenv = EnvGen.ar(
Env.sine,
trig,
timeScale: finalgdur
);
// Hard envelope
var clickenv = EnvGen.ar(
Env([0,1,1,0], [0,1,0]),
trig,
timeScale: finalgdur
);
// Faded
var env = XFade2.ar(sineenv, clickenv, shape.linlin(0.0,1.0,-1.0,1.0));
// Calculate position in buffer
var position = (weight * pos + (posspread * coef)).wrap(0.0,1.0) * BufFrames.ir(buffer);
// Calculate playback rate
var playbackrate = weight * rate * BufRateScale.ir(buffer);
var sig = PlayBuf.ar(
numBufChans,
buffer,
ratespread * (gNum + 1) + 1 * playbackrate,
trig,
position,
loop: 0.0,
doneAction: 0
);
LeakDC.ar(env * sig)
});
// Normalize sound levels a bit
cloud = cloud / numGrains;
cloud = cloud * amp;
Splay.ar(cloud.flatten)
}).set(\buffer, b, \wet1, 0.35).play;
Ndef(\cloud)[1] = \filter -> {|in, verbtime=10, room=5|
JPverb.ar(in, verbtime, 0, room);
};
)
// Gui
(
Spec.add(\pos, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\density, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\rate, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\shape, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\ratespread, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\posspread, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\rand, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\overlap, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Spec.add(\gdur, ControlSpec( minVal: 0.001, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.1));
Spec.add(\wet1, ControlSpec( minVal: 0.0, maxVal: 1.0, warp: \lin, step: 0.0, default: 0.5));
Ndef(\cloud).gui;
)
// Add some modulation
(
Ndef(\saw, {|f=0.001| LFSaw.kr(f).unipolar}).copy(\saw2).set(\f, 0.001);
Ndef(\sine, {|f=0.0151| SinOsc.kr(f).unipolar}).copy(\sine2).set(\f, 0.093211);
Ndef(\cloud).map(\posspread, Ndef(\saw), \stochastic, Ndef(\saw2), \rand, Ndef(\sine), \density, Ndef(\sine2));
)
@capital-G
Copy link

Thanks @madskjeldgaard for sharing this. Have you considered using live input here as well? I am trying to find a way to implement this with BufWr and BufRd but there are some problems as I'll get somewhere some clicky sounds?

(
Ndef(\liveClouds, {
	//var sig = SinOsc.ar(200.0) * EnvGen.kr(Env.perc(releaseTime: 0.2), gate: Impulse.kr(0.25));
	var sig = Ndef.ar(\testSignal, 1);
	
	// internal buffer
	var seconds = 8.0;
	var localBuf = LocalBuf.new(numFrames: SampleRate.ir * seconds, numChannels: 1);
	
	// use a normal phasor to write to our buffer
	var writeFrame = BufWr.ar(inputArray: sig, bufnum: localBuf, phase: Phasor.ar(
		trig: 0,
		rate: 1.0,
		start: 0,
		end: BufFrames.ir(localBuf)
	));
	
	var grains = 16.collect({
		// delayFrames is the number of frames/samples we will wait
		var delayFrames = LFDNoise1.ar(0.02).range(
			BufFrames.ir(localBuf)/8,
			// omit block size as this can clash with the writing header
			BufFrames.ir(localBuf)-(s.options.blockSize*2),
		);
		// reverse the read phasor position by delayFrames, but do not go below
		// 0, therefore mod to stay in range of our buffer
		// w/o -1 we get a glitchy sound?
		var frameDelayPos = (writeFrame - 1 - delayFrames)%(BufFrames.ir(localBuf));
		// rand speed can not go below 1.0 as otherwhise we the writing buffer will
		// catch up with the reading one which will result in a noisy sound
		var randSpeed = LFDNoise1.ar(0.1).range(2.0, 4.0);
		var phasor = Phasor.ar(
			trig: 0,
			rate: randSpeed,
			start: frameDelayPos,
			end: writeFrame,
		);
		
		// create an envelope with the length of our delaytime
		var env = EnvGen.ar(
			envelope: Env([0, 1, 0], [(delayFrames/SampleRate.ir)/2, delayFrames/SampleRate.ir/2], curve: \lin),
			// trigger when our read phasor gets back to the start
			gate: (phasor >= frameDelayPos) * (phasor <= (frameDelayPos + 2)),
			timeScale: randSpeed,
		);
		
		var grain = BufRd.ar(
			numChannels: 1,
			bufnum: localBuf,
			phase: phasor,
			interpolation: 4,
		);
		grain * env;
	});
	// (Splay.ar(grains) + sig)*0.4;
	Splay.ar(grains);
}).play;
)

I think this is b/c the start/end parameters of Phasor are not modulateable? Consider this where we start in different positions within the SinOsc, yet we get the same pattern on both channels.

{
	Phasor.ar(start: 0.0, end: SampleRate.ir/4 * SinOsc.ar(SampleRate.ir/8, phase: [0, pi/2]).abs);
}.plot(1.0);

grafik

or this, a sine wave riding on top of the end of the envelope

(
{
	Phasor.ar(trig: 0.0, rate: 1.0, start: Line.ar(0.0, 1.0), end: Line.ar(0.0, 1.0) + SinOsc.ar(5).abs);
}.plot(0.2);
)

grafik

where the highest value should form a barrier like

(
{
	Line.ar()+SinOsc.ar(5).abs;
}.plot(0.2)
)

grafik

which is not the case if you look closely enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment