Can one add a release with a single instance of Pmono?

I have written the following code, which takes a micro sound length section of a sample and then gradually augments it by doubling until it is the full length of the sample. The code only invokes a single instance of the SynthDef.

When the sample length changes it is very abrupt, so I had the idea that at the point of changing from one sample length to another, the older - and therefore shorter - sample could gradually fade out its volume. I’m happy with my current code but worry I might have locked myself in with it, since the synth never uses the release from the Env, as there is only one instance of the SynthDef.

Is there an elegant way to achieve this without rewriting all the code? Something I have tried unsuccessfully to do using Task and do.

Many thanks for any tips. Here is the code.

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";
~b0 = Buffer.read(s, p);
~b0.play;

(
SynthDef.new (\voice, {
arg amp=1, out=0, gate=1, buf, start, end, rate=1, dur;
var eg = EnvGen.kr(Env.asr(0.001, 1, 0.001), gate, doneAction: 2);
var sig, ptr;
ptr = Phasor.ar(0, BufRateScale.kr(buf)*rate, start, end);
sig = BufRd.ar(2, buf, ptr);
sig = sig * amp;
Out.ar(out, (sig*eg));
}).add;
)

r = 12; // number of repeats
a = Pgeom(1, 2, r).reciprocal;
a = a.asStream.nextN(r);
b = a.reverse;
d = ~b0.numFrames / ~b0.sampleRate;

(
Pmono( \voice,
\buf, ~b0.bufnum,
\dur, Pseq([d], r),
\amp, 0.3,
\start, 0,
\end, Pseq(b*~b0.numFrames, 1),
\rate, 1).play;
);

So the clicks are caused by not windowing the grain… you are essentially doing a reduced type of granular synthesis here.

Below a new envelope is used called window. its read like a buffer using a ptr - this is done with IEnvGen. If your not familiar with windowing have a look at granular synthesis, it basically fades each segment in and out.

The problem with creating many different synths to play this, would be the sheer number required to play grains of such sort durations so that they sound static. GrainBuf is designed for that and it would be worth trying to migrate this to a granular approach.
Alternatively, you could create one synth instance for each buffer duration, and release theirs gates as needed. This should be pretty easy with Task, post your attempt?

s.boot;

~b0 = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

(
SynthDef.new (\voice, {
	var playback_ptr = Phasor.ar(0, BufRateScale.kr(\buf.kr) * \rate.kr, \start.kr, \end.kr);
	var window_ptr = playback_ptr.linlin(\start.kr, \end.kr, 0, 1);
	var window = IEnvGen.ar(Env.sine(), window_ptr); // try a different Env type here! Just make sure the total duration of the env is 1
	var sig = BufRd.ar(1, \buf.kr, playback_ptr) * \amp.kr * window;
	Out.ar(\out.kr, sig);
}).add;
)

(
var number_of_repeats = 12; 

Pmono( \voice,
	\buf, ~b0,
	\dur, ~b0.numFrames / ~b0.sampleRate,
	\amp, 0.3,
	\start, 0,
	\end, Pseq( number_of_repeats.collect({|i| ~b0.numFrames / 2.pow(i) }).reverse, 1),
	\rate, 1
).play;

);

J

2 Likes

Also, try replacing the Env.sine() with something else… Env.perc(0.01, 0.99) or inversely Env([0,1,0], [0.99, 0.01], 4)

1 Like

Sorry, realised I didn’t quite answer your question @domaversano
Here’s what it might look like with Task.

s.boot;

~b0 = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

(
SynthDef.new (\voice, {
	var main_env = EnvGen.kr(Env.asr(0.2, 1, 0.2), \gate.kr(1), doneAction: 2);
	var playback_ptr = Phasor.ar(0, BufRateScale.kr(\buf.kr) * \rate.kr(1), \start.kr, \end.kr);
	var window_ptr = playback_ptr.linlin(\start.kr, \end.kr, 0, 1);
	var window = IEnvGen.ar(Env.sine(), window_ptr); // try a different Env type here! Just make sure the total duration of the env is 1
	var sig = BufRd.ar(1, \buf.kr, playback_ptr) * \amp.kr * window;
	Out.ar(\out.kr, sig * main_env);
}).add;
)

(
Task({
	var number_of_repeats = 12; 
	var end_positions = number_of_repeats.collect({|i| ~b0.numFrames / 2.pow(i) }).reverse;
	var dur_for_change = ~b0.numFrames / ~b0.sampleRate;
	end_positions.do {
		|end_pos|
		var synth = Synth(\voice, [\start, 0, \end, end_pos, \buf, ~b0, \amp, 0.3]);
		dur_for_change.wait;
		synth.set(\gate, 0);
	}
}).play
)

s.plotTree

Also, if you want different durations for each grains you can do something nice with flop.

(
Task({
	var number_of_repeats = 12; 
	var end_positions = number_of_repeats.collect({|i| ~b0.numFrames / 2.pow(i) }).reverse;
	var durations = 12.collect{|i| ~b0.numFrames / ~b0.sampleRate };
	
	[end_positions, durations].flop.do {
		|args|
		var end_pos = args[0];
		var dur = args[1];
		var synth = Synth(\voice, [\start, 0, \end, end_pos, \buf, ~b0, \amp, 0.3]);
		dur.wait;
		synth.set(\gate, 0);
	};
	
}).play
)
1 Like

Hi Jordan,

Thank you, this code is sublime. I’m having to break it all down as it’s a different, and much more elegant approach, than what I am used to doing.

I think my question was unclear however, although it’s very close to doing what I am after, and I’m going to try to get it to that point. Basically I’m thinking in terms of fading out/release enveloping the entire Synthdef, not just the grains. So when it changes from one grain length / sample rate to another it does so by the new one appearing, and the previous one then fading out, as opposed to the somewhat abrupt change my original designs created.

Once again, thank you. I really like the way you have organised the code - it’s much easier to read and comprehend.

You should be able to get what you’re after using the second example I posted? Try changing the main_env fade in and out times to be longer? This will produce an amplitude blend between the two.

var main_env = EnvGen.kr(Env.asr(10, 1, 10), \gate.kr(1), doneAction: 2);

You’re absolutely right, of course. Just been wrapping my head around the way you’ve recoded it. Such a lesson in itself on clarity!

Thanks so much Jordan.