Pfset abd Buffer.sendCollection

Does anybody know why Buffer.sendCollection doesn’t work in Pfset’s func
Works with .loadCollection though.
Related to “streaming” I guess, but couldn’t find much about how to deal with such cases

so, for the following Pfset:

(
SynthDef(\foo, { |out=0|
    var a = SinOsc.ar(\freq.kr(444) * [0.99, 1.01]);
	a = a * EnvGen.kr(Env.perc(0.01, 1), doneAction: 2);
    Out.ar(out, a * 0.5)
}).add;
SynthDef(\fx, { |out=0|
    var a = In.ar(\inBus.kr(), 2);
	a = Shaper.ar(\buf.kr, a);
    Out.ar(out, a)
}).add;
)

(
Pfset(
	func: {
		var sz = rrand(1, 5);
		var tf = Signal.sineFill(
			size: 513,
			amplitudes: Array.fill(sz, {rrand(0.1,1.0)}),
			phases: Array.fill(sz, {rrand(0.0, 2*pi)})
		);
		tf = tf.normalize(-1.0,1.0).asWavetableNoWrap;
		~buffer = Buffer.sendCollection(s, tf, 1);
		~inBus = Bus.audio(s, 2);
		~x = Synth(\fx, [
			\inBus, ~inBus.index,
			\out, 0,
			\buf, ~buffer.bufnum
		], addAction: \addToTail);
	},
	pattern: Pbind(
		\instrument , \foo,
		\out		, Pkey(\inBus),
		\dur        , 0.25,
		\scale      , Scale.major,
		\root       , 0,
		\degree     , Pseq([0,2,4,6], inf),
		\octave     , 4
	),
	cleanupFunc: {
		~x.free;
		~inBus.free;
		~buffer.free;
	}
).play

)

I get:

-> an EventStreamPlayer
ERROR: Message 'playAndDelta' not understood.
RECEIVER:
   Symbol 'hang'
ARGS:
Instance of EventStreamCleanup {    (0x55851df262e8, gc=DC, fmt=00, flg=00, set=02)
  instance variables [1]
    functions : instance of IdentitySet (0x55851da249a8, size=2, set=2)
}
   false

PROTECTED CALL STACK:
	Meta_MethodError:new	0x55851a75b0c0
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = hang
	Meta_DoesNotUnderstandError:new	0x55851a75d400
		arg this = DoesNotUnderstandError
		arg receiver = hang
		arg selector = playAndDelta
		arg args = [ an EventStreamCleanup, false ]
	Object:doesNotUnderstand	0x55851916fac0
		arg this = hang
		arg selector = playAndDelta
		arg args = nil
	EventStreamPlayer:prNext	0x55851b4f0640
		arg this = an EventStreamPlayer
		arg inTime = 2225.771107881
		var nextTime = nil
		var outEvent = hang
	a FunctionDef	0x55851b4ee840
		sourceCode = "<an open Function>"
		arg inTime = 2225.771107881
	Routine:prStart	0x55851b550680
		arg this = a Routine
		arg inval = 2225.771107881

CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Object:doesNotUnderstand
		arg this = 'hang'
		arg selector = 'playAndDelta'
		arg args = [*2]
	EventStreamPlayer:prNext
		arg this = <instance of EventStreamPlayer>
		arg inTime = 2225.771107881
		var nextTime = nil
		var outEvent = 'hang'
	< FunctionDef in Method EventStreamPlayer:init >
		arg inTime = 2225.771107881
	Routine:prStart
		arg this = <instance of Routine>
		arg inval = 2225.771107881
^^ ERROR: Message 'playAndDelta' not understood.
RECEIVER: hang



dont have an answer, but really miss your youtube videos!

1 Like

It can be made to work, but you probably shouldn’t.

A pattern being played must yield events (or nil, to stop).

Buffer.sendCollection uses s.sync to wait until buffer memory is allocated before sending data. This step can’t be eliminated. It’s also internal to the class method’s implementation, so you don’t have any control over how the sync is performed.

s.sync does \hang.yield to force the thread to pause (not reschedule) without permanently ending.

\hang isn’t an event. So EventStreamPlayer chokes on it. It tries to play this entity as if it were an event, but can’t: “‘playAndDelta’ not understood. RECEIVER: Symbol ‘hang’”.

To do “sync in a pattern,” there needs to be an event to pause the thread – so that EventStreamPlayer gets the event and plays it as normal, and sync occurs within the context of the event. This type of event was discussed some years ago, but I think it was never permanently added.

But event types can be added as needed:

(
Event.addEventType(\sync, {
    if(~timeOut.notNil) {
        ~cond.waitFor(~timeOut, ~predicate)
    } {
        ~cond.wait(~predicate)
    }
}, (dur: 0));
)

Then:

p = Pfset(
    func: {
        var sz = rrand(1, 5);
        var tf = Signal.sineFill(
            size: 513,
            amplitudes: Array.fill(sz, {rrand(0.1,1.0)}),
            phases: Array.fill(sz, {rrand(0.0, 2*pi)})
        );
        var cond = CondVar.new;
        tf = tf.normalize(-1.0,1.0).asWavetableNoWrap;
        fork {
            ~buffer = Buffer.sendCollection(s, tf, 1,
                action: { cond.signalAll }
            );
        };
        (type: \sync, cond: cond).yield;
        ~inBus = Bus.audio(s, 2);
        ... etc...
    },
    ...
).play;

This isn’t pretty, but I can’t think of a way to make it tighter. The inner fork is needed to guard the event stream player thread from the sync operation, and you do need to manage your own CondVar.

But I think the deeper reason for this problem is: Patterns are not designed for resource management. If you try to use patterns for resource management, eventually you will run into sticky situations like this.

So the deeper solution is… avoid using patterns for resource management.

A crucial insight in my SC history is that patterns almost never play in isolation – they depend on resources – so it makes sense to wrap the pattern along with its resources into a container object. play, stop, free etc. are addressed to the container, not to the pattern. My chapter in SC Book v1 is about this. Also I have an old, old tutorial at http://www.dewdrop-world.net/sc3/tutorials/index.php?id=7

I wouldn’t claim that everyone will (or even should) like this idiom – but I will claim that resource initialization problems just about completely disappear for me, because of this. Resources are initialized on demand (when creating an instance of a process), and they are ready by the time you .play it. (By contrast, any sync in Pfset will delay pattern onset by an indeterminate amount = loss of timing precision.) They persist for the lifetime of the process object, and get cleaned up when you delete the instance.

That’s why I said “can be done, but probably shouldn’t”… Pfset looks convenient, but the moment of .play is the wrong moment to be initializing buffers. I sometimes use Pfset for its cleanup function, but never, ever for buffer init.

hjh

James, thank you for the answer, so, if I got it right, putting Buffer.load in Pfset’s func makes it unreliable timingwise. What about Pproto then? I see it has dedicated \table event

Buffer allocation is always asynchronous. There is no escape from this. Pproto doesn’t have any magic here.

So, in a pattern, you have basically 3 choices:

  • Sync the pattern’s thread to the completion of the async operation. The pattern will start allocating at quant time, and the sounding events will be a little late, by some indeterminate amount. IMO this is bad.
  • Don’t sync: Send the allocation/init messages, wait 0 beats, and start playing sounding events. Musical timing is preserved but the first event is likely not to sound correctly. This is also bad.
  • Allocate, then wait some amount of time that is under your control. Then you can start the pattern early by this number of beats, wait until the target time, and musical events happen with correct timing. This is good, but awkward.

I almost wish we didn’t have those “resource” patterns in the library at all. IMO (though, my opinion) it’s a dead end street… or rather… at best, it can be manageable for a small number of patterns and not too many resources. I’m not at all convinced that it will scale up to bigger scenarios (whereas the “process object” that I outlined does scale beautifully to high levels of complexity).

hjh

1 Like

I’ve used this technique before, if you want to keep a declarative style:

(
    Pproto({
        var chord;
        (
            play: {
                "doing something".postln;
                1.wait;
                chord = [1, 3, 5].postln;
                "done".postln;
            }
        ).yield[\chord];
        ~chord = chord;
    }, Pbind(
        \dur, 1/4,
        \degree, Pkey(\chord)
    )).play
)

In summary: you can do your own waiting when a play function is being evaluated. This would ALSO include things like Server:sync, which sendCollection requires. Pproto is an okay construct for this because it’s designed to collect resources first, and then pass those resources to a pattern to run. Obviously, this is going to get you in trouble if you try to embed this inside of other patterns, because it effectively pauses everything for some unspecified amount of time to do your loadCollection - so obviously thing could get out of sync etc. But in a simple case, where you JUST set some stuff up and then start playback, this SHOULD work fine.

Some care is required (this one got me…) because the play function is evaluated in the context of the Event that you’re yielding, and NOT the outer Pproto environment. Normally you’d do something like ~buffer = (...) in your Pproto function tand then this buffer would be available to the subsequent pattern. In this case, I set my value (and you could set your buffer…) to a stack variable and then when I was done set it into the environment. This could also look like:
~buffer = (play: { ~buffer = Buffer.sendCollection(s, tf, 1) }).yield[\buffer]
e.g. you have to reach into the event to grab the buffer. Because it’s Pproto it should handle de-deallocation just fine.

It’s worth noting that resource alloc with patterns can get REALLY weird and complicated, so if it’s easy to have a model where you set up your buffers only once, when the server launches, this is probably the more stable / predictable / low-complexity version.

1 Like

Actually if you want to use the resource cleanup parts of Proto, you want to yield an event that looks like:
(type: \freeBuffer). Check out EventTypesWithCleanup, specifically the \freeBuffer type - this is what will get called when it’s time to clean up, so you need to make sure all of those things are set in the event you yield. Really this just amounts to ~bufNum though.

this is actually what I ended up with, just alloc the needed number of buffers in startup.scd/ServerBoot func
But besides buffer allocation as I got it, allocating buses, starting effect-chain synths should be ok with Pfset/Pproto, isn’t it?

In general, yes.

Just pointing out again that it isn’t an “either-or” situation: either allocate at play time in the pattern, or allocate at boot time.

With ddwChucklib:

// this whole block goes into 'performance-init' code
// run it once at the start of a session
(
SynthDef(\foo, { |out=0|
    var a = SinOsc.ar(\freq.kr(444) * [0.99, 1.01]);
	a = a * EnvGen.kr(Env.perc(0.01, 1), doneAction: 2);
    Out.ar(out, a * 0.5)
}).add;
SynthDef(\fx, { |out=0|
    var a = In.ar(\inBus.kr(), 2);
	a = Shaper.ar(\buf.kr, a);
	
	// NB: Usually shaper needs LeakDC
	// you're getting a click without it!
	Out.ar(out, LeakDC.ar(a))
}).add;

PR(\shaperThing).value = Proto {
	~outBus = 0;
	~prep = {
		var sz = rrand(1, 5);
		var tf = Signal.sineFill(
			size: 513,
			amplitudes: Array.fill(sz, {rrand(0.1,1.0)}),
			phases: Array.fill(sz, {rrand(0.0, 2*pi)})
		);
		tf = tf.normalize(-1.0,1.0).asWavetableNoWrap;
		~buffer = Buffer.sendCollection(s, tf, 1);
		~inBus = Bus.audio(s, 2);
		~x = Synth(\fx, [
			\inBus, ~inBus.index,
			\out, ~outBus,
			\buf, ~buffer.bufnum
		], addAction: \addToTail);
		currentEnvironment
	};
	~asPattern = {
		Pbind(
			\instrument , \foo,
			\out		, ~inBus,
			\dur        , 0.25,
			\scale      , Scale.major,
			\root       , 0,
			\degree     , Pseq([0,2,4,6], inf),
			\octave     , 4
		)
	};
	~freeCleanup = {
		~x.free;
		~inBus.free;
		~buffer.free;
	};	
};
)


// then these bits you'd run during the show

PR(\shaperThing) => BP(\s);

BP(\s).play;

BP(\s).stop;

BP(\s).free;

The shape of this code is not dramatically different from Pfset, as it happens, and it maintains encapsulation, while allocating buffers on demand but before they are needed.

Been working for me for almost 20 years… I’d say it’s tested and proven at this point.

hjh

James, did I get it right, after this code evaluated, you have to run performance.init() ?
Sorry, didn’t yet dive into chucklib…

No – the first block of code defines the object. You only need to run it once at the start of the session (or when redefining). I usually put this into a file like ‘something-init.scd’ and then .load it from the path.

After the prototype exists in the PR collection, then you can use it freely.

hjh