Musical Sound Design In Supercollider: 15 - Microsound[2] - Granular Reverberation

This one is about recursive granulator reverberation as well as timestretching based reverb

12 Likes

@alikthename said it before, but love your tutorials. It’s hard (or impossible) to find tutorials that are above beginner level and yours nail it. There is so much to learn in them, even beyond the main topic of each episode. Thanks for taking the time to make them!

Thank you for your feedback, Alberto!

1 Like

Always look forward to this. Thanks Alik!
-Kelley/Melas/poison0ak

1 Like

I’ll second that @Alberto_Gomez, brilliant tutorials. It’s very interesting to see how you go about your sound design processes, and the way your adding in various human elements into the sound.

I’ve listened to your bandcamp page(s), which sound great. Maybe if you ever feel inspired, it would be interesting to see/watch how you go about your compositional process.

Thanks again and I look forward to more in your tutorial series.

1 Like

Thank you for your comment Joesh. Videos about compositional process tactics are on my list. And I now think about may be it would be better to just Livestream them, or combine streams with edited videos. Have to come up with some variant

3 Likes

Hi,
hope you are fine. I just have a question related to GrainBufJ. Wheres the difference to the “normal” Ugen? I tried to reproduce your idea with Grainbuf and had (i think) problems with grains
at the end of the buffer reading the start of the soundfile when getting over “1”. With GrainBufJ it didnt seem to happen.
have a good day!
flo.

That’s the thing I was talking about. 1. It allows to control whether pos parameter will loop or not. 2. It has grainamp parameter

1 Like

Oh…im a bad listener it seems. I watch again =) Thank you!

Might be I’m a bad explainer. Improvising to much

1 Like

no…not really. works perfectly fine for me now
Thanks a lot for the input!

i was trying to use pan = Demand.ar(trig, 0, Dseq([-1, 1], inf) * panMax); in

grained = GrainBufJ.ar(
		numChannels: 2,
		loop: 1,
		trigger: trig,
		dur: grainDur,
		sndbuf: sndBuf,
		rate: rate,
		pos: readPos,
		interp: 4,
		pan: pan,
	);

when doing so you have to set numChannels:2 i think. But then i get the error
Buffer UGen channel mismatch: expected 2, yet buffer has 1 channels

i declared the reverb bus like this:

~makeBusses = {
	~bus = Dictionary.new;
	~bus.add(\granular_reverb -> Bus.audio(s, 2));
};

what has to be changed here? thanks

ps.: really looking forward to the next episode!

What I see is sc says that buffer number sndBuf has only one channel.
Maybe you did not assign your 2 channel buffer to sndBuf var?

hey thanks for your reply :slight_smile:

i tried to set
sndBuf = {LocalBuf(bufFrames, 2).clear}!2;
when doing so, i get only the dry signal:

(
~bus = Dictionary.new;
~bus.add(\granular_reverb -> Bus.audio(s, 2));
)

(
SynthDef(\granular_reverb, {
	arg out=0, in=0, inAmp=1, outAmp=1, overlap=2,
	tFreq=20, tFreqMF=0, tFreqMD=0,
	rate=1, rateMF=0, rateMD=0,
	offset=0, offsetMF=0, offsetMD=0, minGrainDur=0.001, panMax=0.5;

	var sig, inSig, gDur, readPos, writePos, grained, balance, grainDur, pan;
	var trig, bufFrames, bufRateScale;
	var sndBuf, bufDur;

	var tFreqMod = {
		SinOsc.ar(tFreqMF, Rand(0.0,2pi)) * tFreqMD;
	};

	var rateMod = {
		SinOsc.ar(rateMF, Rand(0.0,2pi)).range(0, rateMD);
	};

	var offsetMod = {
		SinOsc.ar(offsetMF, Rand(0.0,2pi)).range(0, offsetMD);
	};

	tFreq = tFreq + tFreqMod.dup;
	rate = rate - rateMod.dup;

	bufFrames = 2**16;
	sndBuf = {LocalBuf(bufFrames, 2).clear}!2;
	bufDur = BufDur.ir(sndBuf);

	writePos = Phasor.ar(end: bufFrames);

	trig = Impulse.ar(tFreq);
	grainDur = max(tFreq.reciprocal * overlap.lag(5), minGrainDur);
	pan = Demand.ar(trig, 0, Dseq([-1, 1], inf) * panMax);

	readPos = writePos - 64 / bufFrames - offset - offsetMod.dup;
	readPos = Wrap.ar(readPos, 0, 1);

	grained = GrainBufJ.ar(
		numChannels: 2,
		loop: 1,
		trigger: trig,
		dur: grainDur,
		sndbuf: sndBuf,
		rate: rate,
		pos: readPos,
		interp: 4,
		pan: pan,
	);

	grained = HPF.ar(grained, \grHpf.kr(40));
	grained = LPF.ar(grained, \grLpf.kr(15000));

	inSig = In.ar(in, 2) * inAmp;

	// writing granulated + input back to grain buffer
	sndBuf.do { |b i|
		BufWr.ar(grained[i] * \feedback.kr(0.3) + inSig[i], b, writePos)
	};

	sig = Mix([
		grained * \wet.kr(1).lag(3),
		inSig * \dry.kr(1)
	]) * outAmp;

	Out.ar(out, sig.tanh);
}).add;
)

(
SynthDef(\test, {
	arg out=0, pan=0, amp=0.35, freq=150;
	var env = Env.perc.kr(2);
	var sig = Saw.ar(freq);
	sig = Pan2.ar(sig, pan, amp * env);
	Out.ar(out, sig);
}).add;
)

(
Pdef(\granular_reverb,
	Pmono(\granular_reverb,
		\overlap, 5.3,
		\tFreq, 26.60,
		\tFreqMD, 0.0,
		\tFreqMF, 25.10,
		\offset, 0.032,
		\offsetMD, 0.11,
		\offsetMF, 94,
		\rate, 1,
		\rateMD, 0.0,
		\rateMF, 19.80,
		\inAmp, 1,
		\outAmp, 1,
		\grLpf, 14400,
		\grHpf, 93,
		\dry, 1,
		\wet, 0.50,
		\feedback, 0.45,
		\panMax, 0.9,
		\in, ~bus[\granular_reverb],
	)
).play;
)

(
Pdef(\test,
	Pbind(
		\instrument, \test,
		\midinote, 60,
		\dur, 1,
		\out, ~bus[\granular_reverb],
	)
).play;
)
1 Like

Since you want to go with multichannel buffer, you should remove all those .dup methods (or !2) from modulators and LocalBuf. They create multichannel expansion They’re all tailored to the idea of having dedicated GrainBufJ for every channel. But without ruining that idea I’d suggest leaving LocalBufs one-channel and playing with the phase of one of the triggers instead:

trig = Impulse.ar( tFreq, [0, \rightTriggerPhase.kr(0.25)] );

By specifying var tFreqMod = {
SinOsc.ar(tFreqMF, Rand(0.0,2pi)) * tFreqMD;
}; as a function and duplicating it later, we end up having 2 triggers, one for left and one for right channel. And since this function contains Rand these trigger signals will be different once you increase tFreqMD control. You can leave it at 0 ( default value in this synthdef ). and play with Impulse.ar phases instead (or combine these 2 methods)

okay. so leaving sndBuf = {LocalBuf(bufFrames).clear}!2; and numChannels:1 in GrainBufJ and setting pan = Demand.ar(trig, 0, Dseq([-1, 1], inf) * panMax); and playing with trig = Impulse.ar(tFreq, [0, \rightTriggerPhase.kr(0.25)]); or all of these while setting numChannels:2? or leaving pan=0 and playning with trig = Impulse.ar(tFreq, [0, \rightTriggerPhase.kr(0.25)]); ? sorry im just confused by themultichannel expansion. thanks a lot for your help :slight_smile:

Yeah, remove that Demand thing for pan, playing with pan will not do anything with one-channel GrainBuf.
Just play with phase of one of the triggers. And consider reading about multichannel expansion Multichannel Expansion | SuperCollider 3.11.1 Help. Without it it’s difficult to understand synthdefs.

1 Like

Thanks a lot for making Things clear :slight_smile: will read More about Multi Channel Expansion

== You’re wellcome ! ==