Is there a simple workaround for Pbind not "passing down" stream resets?

Contrast

~tm = 0
r = Pfunc({ |ev| ~tm = ~tm + ev[\dur] }, { ~tm = 0 } ).asStream
r.next((dur: 3))
r.reset; ~tm // ~tm is zero again now, as expected

with

r = Pbind(\dur, Pseq([0.4], 5), \acc, Pfunc({ |ev| ~tm = ~tm + ev[\dur] }, { ~tm = 0 } )).asStream
r.next(())
r.reset; ~tm // ~tm is not zero here

Basically, is there a simple way to make Pbind “pass down” resets, without knowing a priori the structure of the inner streams?

The catch here is that streams are almost always Routines. When a Routine is reset, only one thing happens: its status is modified so that it will start over from the beginning of the function. That’s it. There’s no resetFunc for the vast majority of streams. Meaning: for most streams, when you reset them, there is no visible side effect at the moment of being reset. The effect of the reset is seen the next time the Routine is called.

Pbind’s stream is a Routine, and also note in Pbind’s code that it never resets anything. It makes new streams.

FuncStream is structurally different. It has no internal state. But, James McCartney felt some form of reset would be good, so he put in a function to handle that, just calling it directly (meaning any side effects are immediate).

You do have a point here: most patterns, when you call asStream, start over from the beginning. But, if you define “starting over” as a reset, Pfunc doesn’t. It might be legitimate to call the resetFunc upon asStream – this might also break existing user code, so it would have to be discussed.

That suggests one workaround would be to create your own version of Pfunc:

Pfunc2 : Pfunc {
    asStream {
        resetFunc.value;
        ^FuncStream(nextFunc, resetFunc)
    }
}

Alternately, this would work too:

r = Pbind(
    \dur, Pseq([0.4], 5),
    \acc, Plazy {
        ~tm = 0;
        Pfunc({ |ev| ~tm = ~tm + ev[\dur] })
    }
).asStream;

With both of those, you would not see ~tm reset to 0 immediately, but the first next after reset would reinitialize it. (Unfortunately it’s Pfunc’s reset behavior that has misled you here: it, rather than Pbind, is the odd duck.)

hjh

2 Likes

Well, looking through Patterns.sc, one does pass down the reset… Pif resets all sub-streams on its own reset. But that actually seems to be the only one if such subs-stream behavior.

~tm = 0
r = Pif ( true, Pfunc({ |ev| ~tm = ~tm + ev[\dur] }, { ~tm = 0 } ), 0, ()).asStream
r.next((dur: 3))
r.reset; ~tm // ~tm is zero again now, as expected

It’s quite disappointing that not even Pchain does that. (The reset semantics would not be anymore problematic than for Pif, I think.) I guess this is just another “don’t expect consistent behavior in the SC streams libraries”. I suspect reset isn’t used/tested much.

As you said, the issue is that most Patterns return a Routine for their asStream; Pif returns a FuncStream, so it can do reset-passdown… Pkey actually creates a FuncStream too, but without a reset function.

And I don’t see where in the Pbind documentation it says it’s not resetting anything… (It mentions ESP having a reset, duh.)

Pfunc help is misleading actually:

resetFunc Function that is called when the stream is reset. In an event stream receives the current Event as argument.

1 Like

I’d like to suggest an alternate viewpoint.

James McCartney didn’t have to create any of this, and he didn’t have to open-source it the moment he didn’t need to depend on it for his income.

I won’t say there are zero inconsistencies in the interfaces[+], but I will say that overall, the design is far better than most people in the world could come up with by themselves. And it’s certainly true that people have been using this design successfully for 20 years to produce music, sound art, multimedia installations etc.

[+] And some of the inconsistencies arrived later (not part of the original design).

In my opinion (you’re free to disagree), it’s useful to discuss ways a/ to use the existing frameworks, b/ to create additional structures around the existing frameworks to plug holes and c/ to fix legit bugs. I’m happy to help with workarounds and to explain why it is the way it is, that’s it.

I suspect reset isn’t used/tested much.

That’s true. It’s more common to discard the old stream and create a new one. (Which suggests that reset may not be what you’re after – the Plazy{} I suggested above will reinitialize for both asStream and resetting the stream, so I think it’s a more robust approach than trying to beat Pfunc into submission.)

hjh

My apologies, I didn’t mean to come off as ranting/flaming…

Yes, the Plazy { init; Pfunc } solution is probably the best. (Actually, the Pfunc can be just a FuncStream, but that sacrifices typing time for a negligible performance gain :stuck_out_tongue: )

RFluff - as James is always saying, SC is a ‘do-ocracy’… if you have your finger on annoying inconsistencies I’d hope you would jump in with a suggestion - if SC is going to live it needs to be maintained and new user pain points are opportunities.