Fellow ChucKists, A few days ago I was talking with my friend Casper Schipper about the exciting new thing that extending UGens now works. Here are some modest proposals for extending that; * Reserve a ".tick()" function name for automatically overwriting the base class's tick function. Rationale; while we can (as I demonstrated with a quick example on this list) spork a shred that will make a UGen like Step or Impulse output a signal that shred will not be at the right place in the overall execution order as shreds (by definition) can't run while the Ugen-graph updates. Another down-side to that strategy is that unchucking the UGen in that case won't freeze its behaviour nor save CPU. This is inconvenient, it can harm sound quality and generally makes our own work a sort of "second rate citizen". With custom UGens that behave like build-in ones libraries shared by the community wouldn't need much of a extra manual which will make code more consistent and sharing possibly more popular. This special function would be called instead of the normal tick and would -clearly- need to be of type float or a compilation error would be thrown. Overwriting .tick() would be entirely optional, for a class foo that extends LiSa we may just wish to set some behaviour in some default way that we happen to use a lot today and have LiSa herself deal with the processing. * Reserve a "pull" float for pulling any UGens after our Ugen in the graph. Rationale; to preserve correct execution order and to have maximum flexibility in our own creations we'd like our custom UGens to have inputs. Currently this would be very hard to accomplish, even when we disregard UGen-graph execution order. "pull" would be a float that would be computed from our UGen's inputs (depending on our .op), if it's used in the tick function, and replace all uses of that variable in this call of the function. By that I mean we don't tick all of the UGens behind us multiple times just because we need the value a few times. For that reason I think it's more clear to abstract this away as a float, instead of having it be a explicit function. "pull" in the context of a function named "tick" wouldn't need to be declared, I imagine. That's elegant (I think), it's also a exception to what we have so far in that it would be a highly context dependant special variable but UGens are a unusual type in general and even with regard to timing, our big focus, behave quite unusually. So, let's have a example of what this would look like. Let's say I'd like to have a new UGen that would add a small bit of noise to a signal (example intentionally kept simple). I'll write that UGen and use it on a arbitrary input; class AddNoise extends UGen { float amnt; //some internal state fun float tick() //tick function { return (pull * (1 - amnt) + Std.rand2f(-1 * amnt, amnt); //here we pull, if we don't have a input we'll just get some noise but if we wish we could detect that condition and deal with it, } fun float amount(float in) //way of setting the behaviour { if (in > 1) 1 => in; else if (in < 0) 0 => in; in => amnt; return in; } fun float amount() {return amnt;} //overloaded get function as those are standard. } //let's go! adc => AddNoise an => dac; .1 => an.amount; minute => now; <<<"bye!">>>; This, to me, looks simple, expressive and realistic. Particularly I can't see any big things here that would backfire if at some point we'd like to be able to pre-compile external UGens into some sort of module format. This should also -as far as I can see- play nicely with the existing UGen graph functionality, for example the detection of feedback loops. Note that Casper and me were brainstorming about our wishes, any glaring errors in this post are mine. Also note that this proposal could make .tick() a thing you could explicitly call manually, possibly for all UGens. I don't see a big problem with that, it might even be useful and/or fun. Yours, Kas.
participants (1)
-
Kassen