BufWr Looping When Loop == 0

Hi-
I’m going through the example for BufWr.
My understanding is that the following function should not keep recording, as the LFNoise changes the SinOsc freq. It seems to continue recording anyway, though.
I’m curious if this is an oversight on my end or a bug.
Thank you.

(
// allocate a buffer for writinig into
s = Server.local;
s.sendMsg("/b_alloc", 0, 44100 * 2);
)


//write into the buffer with a BufWr
(
y = { arg rate=1, loop=0;
    var in;
	in = SinOsc.ar(LFNoise0.ar(0.5)*400, 0, 0.1);
    BufWr.ar(in, 0, Phasor.ar(0, BufRateScale.kr(0) * rate, 0, BufFrames.kr(0)), loop);
    0.0 //quiet
}.play;

x = { arg rate=1;
    BufRd.ar(1, 0, Phasor.ar(0, BufRateScale.kr(0) * rate, 0, BufFrames.kr(0)))
}.play(s);
)

The Phasor is looping, not the BufWr.

If you want it to stop at the end, try Line or Sweep instead (let the phase go past the end of the buffer – then loop: 0 will make a difference).

hjh

1 Like

OK - I think I partially understand.
Does the loop command activates a “trigger” in a uGen, such as Sweep or Line in the Phase argument?
I guess the question would be if there are other behaviors that can be looped. If I wanted to trigger a random envelope, instead of a Sweep, is there a way to do that?

No. It’s not a command – it’s a parameter. The only thing loop can do here is to tell BufWr how to process its inputs. In this context, it means, if the phase input is out of range (negative, or >= numFrames) and loop > 0, then the phase input will be wrapped back into the range 0 <= phase < numFrames. loop has no effect on an in-range phase value. Phasor provides an in-range value, so loop doesn’t do anything here.

An input to a UGen can affect the behavior of only that UGen, nothing else. Your suggestion is that in Sweep → BufWr, a value provided to BufWr could modify the way that Sweep behaves. This is completely impossible because Sweep has already evaluated before BufWr does. Any downstream UGens receive the output, but can’t directly receive parameters unless they’re also passed in.

The correct way to retrigger a non-looping BufWr is to generate the trigger by yourself, and feed this into Sweep.

hjh

1 Like

Thanks @jamshark70 - this makes a lot more sense now.

I’m still not quite understanding a few things as a newbie, though - so I did want to try an build off of this a bit.

My goal, ultimately, is to sample a random signal to record non-linearly with BufWr. In order to do that, my thinking is that I need to use another BufWr with a LFDNoise uGen that is triggered and recorded via Sweep.

What I’m noticing here is that there is a gap now, about 1 second long, any time Sweep is triggered…and that it seems to continually be changing the buffer, even without a trigger.

Thanks again for all the help!

~b = Buffer.alloc(s, 48000*2, 1);

//write into the buffer with a BufWr
(
y = { arg rate=1, loop=0, trig=0;
    var in;
	in = LFDNoise0.ar(3);
	BufWr.ar(in, ~b, Sweep.ar(trig, BufRateScale.kr(~b) * rate).range(0, BufFrames.kr(~b)), loop);
    0.0 //quiet
}.play;

)

~b.plot

I think you’re close to something like this. Take a look at this Buffer:


~b = Buffer.alloc(s, s.sampleRate * 4, 1);


(
SynthDef(\bufWrExample, { arg buf, rate = 1, loop = 0;
	var signal, pos, trig, phase;
	
	// Generate random triggers
	trig = Dust.kr(0.5); 
	
	// Generate a random signal
	signal = LFDNoise0.ar(3) + PinkNoise.ar(0.1);
	
	// Use Phasor to create a continuous phase that wraps around the buffer length
	phase = Phasor.ar(trig, BufRateScale.kr(buf) * rate, 0, BufFrames.kr(buf));
	

	BufWr.ar(signal, buf, phase, loop);
	
	0.0;
}).add;
)



x = Synth(\bufWrExample, [
	\buf, ~b,
	\rate, 1,
	\loop, 1
]);


// wait a moment, so the buffer is filled 
~b.plot;

Maybe if you alternate between the signal you want to record and silence, would be more or less what you described? I’m not sure I understood, but maybe the code helps you somehow.

Hi @smoge -

This is very close to what I’d like to do - it’s just that I can’t seem to figure out how to stop and store the buffer. If I rewrite your code to remove the loop and to prevent retriggering, I am still noticing the the buffer ~b is getting updated periodically. I think this is because Phasor.ar keeps going, no matter what. Substituting Phasor.ar with Sweep.ar works, but it adds this one second of silent gap to the buffer, as mentioned in the previous post…
I am sure I am overlooking something very silly that seasoned SC users will spot!

~b = Buffer.alloc(s, s.sampleRate * 4, 1);

(
SynthDef(\bufWrExample, { arg buf, rate = 1, loop = 0, trig = 0;
	var signal, pos, phase;
	
	// Generate a random signal
	signal = LFDNoise0.ar(3) + PinkNoise.ar(0.1);
	
	// Use Phasor to create a continuous phase that wraps around the buffer length
	//phase = Phasor.ar(trig, BufRateScale.kr(buf) * rate, 0, BufFrames.kr(buf));
	phase = Sweep.ar(trig, BufRateScale.kr(buf)*rate).range(0, BufFrames.kr(buf));
	BufWr.ar(signal, buf, phase, loop);
	
	0.0;
}).add;
)



x = Synth(\bufWrExample, [
	\buf, ~b,
	\rate, 1,
	\loop, 0
]);

x.set(\trig, 0);
x.set(\trig, 1);
// wait a moment, so the buffer is filled 
~b.plot;

Phasor is a sawtooth. If it doesn’t loop, then it’s no longer a sawtooth.

That is, if you want non-looping behavior, Phasor is decidedly not the right phase source.

If you supply a looping phase source (e.g. Phasor) into BufWr, then there is nothing BufWr can do to prevent the recording from looping. BufWr only sticks data into the buffer in the place where it’s being told to do so, by the phase input. It cannot retroactively modify the phase source.

Sweep.ar().range isn’t valid.

range depends on each UGen’s signalRange method to determine whether it’s expected to be \bipolar (-1 … 1) or \unipolar (0 … 1).

Sweep, of course, doesn’t obey either of those ranges, because it’s designed to sweep upward indefinitely – no limit – from 0.

This is one of those interfaces in SC that could be argued to be incomplete. Probably there should be:

+ Sweep {
	signalRange {
		Error("'.range' is not valid for UGen Sweep").throw
	}
}

Instead, you get bipolar. So it’s assumed that -1 should map onto phase 0, and 1 onto BufFrames.kr(buf). When Sweep starts at 0, this maps onto the middle of the buffer.

But then, running the range up to BufFrames.kr(buf) is also not valid, because one second of elapsed time will cover the entire 4 seconds of buffer. That will probably write every 4th sample and leave the others untouched.

sc-bad-write

Pretty sure that’s not what you wanted.

IMO BufWr with rate != 1 is pretty much never a good idea. Rate > 1 will skip over samples; rate < 1 only downsamples, rather crudely.

I’d recommend Sweep.ar(trig, SampleRate.ir) instead – SampleRate.ir inside parens, because then the per-sample increment should be 1.0 = better accuracy. (Sweep.ar(trig, 1) would add 1 / sr per sample, and you’re almost certain to run into floating point rounding error.)

hjh

1 Like

If, hypthetically, I like the crude down-sampling and skipping over samples… is there a way to do this? I noticed that I like the sound of the LFDNoise controlling the BufWr/BufRd functions, but that I couldn’t get it to repeat itself or “stop”, which is how I ended up in this rabbit hole.

If I understand what you’re saying correctly, it sounds like there’s no “good” way to scale Sweep to the BufFrames.kr(buf) range, but that replacing line 12 with the following partially “works” :

	phase = Sweep.ar(trig, BufRateScale.kr(buf)*rate) * BufFrames.kr(buf);

It does remove the “gap”, at least… but using a sampled random waveform as a “guide” for successive iterations of BufRd and BufWr seem riddled with difficulty.

I was thinking the following could work, but with the Sweep.ar synchronized, I suspect that’s at least one reason I’m not getting the sound I’m expecting.

Should I be using a different uGen for this or is the idea just weird?

~b = Buffer.alloc(s, s.sampleRate * 4, 1);

(
SynthDef(\bufWrExample, { arg buf, rate = 1, loop = 0, trig = 0, trig2;
	var signal, pos, phase, phase2, sig2, lb;

	// Generate a random signal
	signal = LFDNoise1.ar(0.4) + PinkNoise.ar(0.1);
	lb = LocalBuf(48000*4, 1);
	
	// write it into a localbuffer and scale the sweep across the BufFrames. 
	phase = Sweep.ar(trig, BufRateScale.kr(lb)*rate) * BufFrames.kr(lb);
	BufWr.ar(signal, lb, phase, loop);
	

	//take a fixed signal.
	sig2 = SinOsc.ar(1000);
	
	//write a buffer using the sampled noise buffer as a way of breaking the linearity. 
	BufWr.ar(sig2, buf, BufRd.ar(1, lb, phase*BufFrames.kr(lb))*BufFrames.kr(buf));

	Out.ar(0, BufRd.ar(1, buf, Phasor.ar(1, 4, 0, BufFrames.kr(buf))));
}).add;
)



x = Synth(\bufWrExample, [
	\buf, ~b,
	\rate, 1,
	\loop, 0
]);

x.set(\trig, 0);
x.set(\trig, 1);

// wait a moment, so the buffer is filled
~b.plot;

Yea, I thought something like LFClipNoise.ar(3) > 0 (alternating (jumping) between 1 and 0) could help you, but I think it will not really “stop” BufWr. I’m not sure the implementation expects a zero and stop writing numbers. Maybe it would need to be a new UGen, or we would need to take a look at the code to see how that works.

I never tested any of that, but I think that it may be possible with demand rate ugens, like Dbufwr, which have special cases for number of samples = 0 (ResetInput). You can give it a shot and see what is possible.

1 Like

The output range of Sweep depends on how much time passed since the last trigger, and the rate: rate * time. If you decide those in advance, then you can predict the value of Sweep at the end of that duration, and scale by numframes / end_value.

Otherwise the end value is unknown, and you can’t meaningfully scale an unknown range onto a known one.

The closest you can get to stopping is to force the bufwr phase above numframes. It will still write into the buffer, but only the last sample, which probably won’t matter most of the time: phase + (stop_signal * 1e20) would do this when stop_signal is set to a positive value.

Repeating is retriggering the Sweep.

BufWr cannot stop writing, and unfortunately with loop == 0, it clips the phase to 0 <= phase < numFrames, so you can’t use out of range phase values to disable writing. The only way seems to be to pause or free the node.

IMO that’s a design mistake: a “run” parameter would be very welcome, but we don’t have one. (Or, don’t clip and simply not write to non-existent addresses.) JMc was probably trying to save an “if” test but I think it would be nicer if we could disable recording while the synth is still running.

hjh

1 Like