GrainBuf's envbufnum makes no difference?

I read in the docs that all parameters of GrainBuf except with numChannels and trigger are polled at grain creation time, and I expected to hear the difference when re-evaluating my grain’s envbufnum argument (e.g. it’s release-time), but (at least to my ears) this is not the case. Can some one give me a hint, whether I am expecting something wrong with respect to my code below?

(
var winenv;
winenv = Env.perc(attackTime: 0, releaseTime: 2);
z = Buffer.sendCollection(s, winenv.discretize, 1);
)
(
{
	var trig = Dust.ar(MouseX.kr(0.1, 50, 1));
	GrainBuf.ar(
		numChannels: 2,
		trigger: trig,
		dur: trig.reciprocal,
		sndbuf: b,
		rate: BufRateScale.ir(b) * LFNoise1.kr(10).range(0.5, MouseY.kr(1, 8)),
		pos: 0,
		interp: 2,
		pan: 0,
		envbufnum: z,
		maxGrains: 2.pow(16)
	)
}.play
)

The envelope’s total duration is scaled to match the given dur.

attackTime 0 means that the full duration will be given to the release segment. In that case, it doesn’t matter if you specified 1 or 2 – in both cases you’ll get the same ramp from 1 to 0, in dur seconds.

You would hear a difference by changing the attack vs decay ratio – attack 0.1, release 0.9 will sound very different from attack 0.9, release 0.1. (In your example, attack / release always = 0, hence, no difference in ratio, hence no difference in sound.)

hjh

1 Like

Great information! Some further questions I have:

  • My question was if the envbufnum is being retrieved at grain creation time, I had expected to hear the difference while the synth is running, if I re-evaluate the Env with different attack/release times, but this is not the case.
  • Second question: Your information seems to be very much from the point-of-view of a SC-developer to me! Since nowhere in the documentations there are info about the “scaling” of the envelope’s duration. Would you like to share with me and other readers of this thread, where you found these fine information? That would be great!

Many thanks in advance!

The following example shows that the envelope buffer’s contents are definitely respected – even changing mid-grain.

I guess the problem is that you’ve hard-coded a bufnum for z into the synth, but then you change the bufnum by doing Buffer.sendCollection, and you never informed the synth of the new bufnum.

s.boot;

(
fork {
	var cond = CondVar.new;
	b = Buffer.alloc(s, 0.025 * s.sampleRate, 1);
	c = Buffer.alloc(s, 2 * s.sampleRate, 1, completionMessage: { |buf| buf.fillMsg(0, buf.numFrames, 1) });
	z = Buffer.alloc(s, 512, 1);
	
	SynthDef(\test, {
		var frames = BufFrames.kr(c);
		var trig = Impulse.ar(1 / 0.005);
		var dur = 0.005;
		var sig = GrainBuf.ar(
			1, trig, dur, 
			c, 1,
			Phasor.ar(0, 1, 0, frames) / frames,
			4,
			envbufnum: z
		);
		RecordBuf.ar(sig, b, run: 1, loop: 0, doneAction: 2);
	}).add;
	
	s.sync;
	
	z.setn(0, Env([0, 1, 0], [0.1, 0.9], -4).discretize(z.numFrames));
	s.bind { a = Synth(\test) };
	0.0125.wait;
	s.bind { z.setn(0, Env([0, 1, 0], [0.9, 0.1], -4).discretize(z.numFrames)) };
	
	a.onFree { cond.signalAll };
	cond.wait;
	
	fork {
		b.getToFloatArray(0, -1, -1, 5, { |data|
			defer { data.plot };
			cond.signalAll;
		});
	};
	cond.wait;
	b.free;
	c.free;
	z.free;
};
)

Indeed that seems to be assumed. But… the grain’s duration is fully specified by dur. What would be the point of having a dur argument if the envelope could override it?

Also note the behavior of discretize: the Env object’s duration is lost at this point. You only have an array of some size, but there is no concrete relationship between the array size and the Env duration. You could discretize to a different size and this would not encode anything about the Env duration.

hjh

Anyway there are a few things to fix:

b = Buffer.sendCollection(s, Signal.fill(4096, { |i| sin(i * (2pi * 16/4096)) }));

(
var winenv;
winenv = Env.perc(attackTime: 0.1, releaseTime: 0.9);
z = Buffer.sendCollection(s, winenv.discretize, 1);
)
(
// if you're going to change the envbufnum,
// you need a synth arg for it
// also if you're going to update things in the synth,
// you can't do that if you haven't saved the synth in a variable
// freestanding {}.play is IMO not a good habit
a = { |z|
	var trig = Dust.ar(MouseX.kr(0.1, 50, 1));
	GrainBuf.ar(
		numChannels: 2,
		trigger: trig,
		// ensure reasonable boundaries
		dur: trig.reciprocal.clip(0.1, 2),
		sndbuf: b,
		rate: BufRateScale.ir(b) * LFNoise1.kr(10).range(0.5, MouseY.kr(1, 8)),
		pos: 0,
		interp: 2,
		pan: 0,
		envbufnum: z,  // this is the *arg* z, not the outside variable
		maxGrains: 2.pow(16)
	) * 0.1
}.play(args: [z: z]);
)

(
fork {
	var winenv;
	var cond = CondVar.new;
	winenv = Env.perc(attackTime: 0.9, releaseTime: 0.1);  // reverse envelope
	// sendCollection + CondVar is a bit of trouble
	fork {
		z = Buffer.sendCollection(s, winenv.discretize, 1, action: {
			cond.signalAll
		});
	};
	cond.wait;
	a.set(\z, z);
};
)

hjh