instrument in function - good idea, or?
Hi I know I'm a bit slow, but I just found that it's quite simple to totally encapsulate an instrument in a function, ugens and all, as the example below shows. Now I'm wondering how much overhead this yields compared to keeping the ugens, ADSR.set and stuff like that outside the function? I guess part of the answer is it depends on what one needs to do. Do you need to play the instrument monophonically, with a small, fixed number of voices or ad-hoc polyphony. The instrument-in-a-function solution elegantly solves the polyphony problem, I can just spork as many voices as I need, and they are automatically destroyed when finished. But how great a peak in cpu usage does the creation of the ugens generate? 5 => int octave; .1 => float gain; [0,2,4,7,9,11,14,16] @=> int notes[]; while(true){ notes[Std.rand2(0,notes.cap()-1)] + 12*octave => int note; ms * Std.rand2f(1500,2000) => dur length; spork ~ ep(note, length, gain); ms * Std.rand2f(150,1500) => now; } fun void ep(int note, dur length, float gain){ 2::second => dur decay; 10::ms => dur release; SinOsc s1 => ADSR e1 => dac; SinOsc s2 => ADSR e2 => dac; gain => s1.gain; gain * .2 => s2.gain; e1.set(1::ms, decay, 0, release); e2.set(1::ms, decay * .3, 0, release); Std.mtof(note) => s1.freq; s1.freq() * 2=> s2.freq; e1.keyOn(); e2.keyOn(); if(length < decay){ length => now; e1.keyOff(); e2.keyOff(); release => now; } else { decay => now; } } -- Atte http://atte.dk http://modlys.dk http://virb.com/atte
I'm not sure about this, but I have a feeling that s1, s2, e1 and e2 aren't
destroyed or unchucked when you leave the function. A quick experiement
(remove the keyOffs) could tell if this is so. This means that with every
call to ep a set of oscillators will be created and never stop, making you
run out of resources eventually.
Comparing with Java's garbage collection, as long as an object is used
somewhere (e.g. chucked to a dac), it won't be destroyed and garbage
collected.
Please correct me if I've got this wrong.
/Stefan
On Thu, Oct 8, 2009 at 8:58 AM, Atte Andre Jensen
Hi
I know I'm a bit slow, but I just found that it's quite simple to totally encapsulate an instrument in a function, ugens and all, as the example below shows. Now I'm wondering how much overhead this yields compared to keeping the ugens, ADSR.set and stuff like that outside the function?
I guess part of the answer is it depends on what one needs to do. Do you need to play the instrument monophonically, with a small, fixed number of voices or ad-hoc polyphony. The instrument-in-a-function solution elegantly solves the polyphony problem, I can just spork as many voices as I need, and they are automatically destroyed when finished. But how great a peak in cpu usage does the creation of the ugens generate?
5 => int octave; .1 => float gain; [0,2,4,7,9,11,14,16] @=> int notes[];
while(true){ notes[Std.rand2(0,notes.cap()-1)] + 12*octave => int note; ms * Std.rand2f(1500,2000) => dur length; spork ~ ep(note, length, gain); ms * Std.rand2f(150,1500) => now; }
fun void ep(int note, dur length, float gain){ 2::second => dur decay; 10::ms => dur release; SinOsc s1 => ADSR e1 => dac; SinOsc s2 => ADSR e2 => dac; gain => s1.gain; gain * .2 => s2.gain; e1.set(1::ms, decay, 0, release); e2.set(1::ms, decay * .3, 0, release); Std.mtof(note) => s1.freq; s1.freq() * 2=> s2.freq; e1.keyOn(); e2.keyOn(); if(length < decay){ length => now; e1.keyOff(); e2.keyOff(); release => now; } else { decay => now; } }
-- Atte
http://atte.dk http://modlys.dk http://virb.com/atte _______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
-- Release me, insect, or I will destroy the Cosmos!
Stefan Blixt wrote:
I'm not sure about this, but I have a feeling that s1, s2, e1 and e2 aren't destroyed or unchucked when you leave the function.
It might be. A safe way would be to simply unchuck them as the very last thing in the function, which I'm pretty sure should do the trick.
A quick experiement (remove the keyOffs) could tell if this is so.
I think they are destroyed when the spork finishes. I tried playing around with the example, and sound stops when the function exits and the shreds exits (according to miniAuricles virtual machine window. But certainty from someone more knowledgeable than me, would be appreciated :-) -- Atte http://atte.dk http://modlys.dk http://virb.com/atte
Normally UGens are defined within the scope of a shred, this may be a sporked function or the "main shred", being the code in a file outside of any functions or classes. When the shred a UGen was defined in exits the UGen will be disconnected from anything it connected to (I'm not sure everything that connects to the Ugen on the other end will also be disconected but that doesn't matter much here). Because of this the DAC will no longer poll it for new samples so it won't take any CPU anymore. This, in adition to calculation order, is the big advantage of the "pull through" model we use for the UGens. CPU-wise you should be fine but the memory used for these UGens probably still won't be freed. I think that in this case you should be able to free the memory by assigning "null" to the UGen instances; I never tried that myself in practice but it was supposed to work. That leaves object creation as a potential source of CPU spikes. You certainly *can* cause glitches by creating objects but it doesn't need to happen. I think results will depend on the complexity of a single voice, the amount of voices in use at the time, the speed of the CPU and the size of your buffer (which also affects latency). I don't think there is a substitute for simply trying it out and seeing how far you get. In this case I'd take that route, if/when you also add note-off signals instead of knowing the length at the start you will need a infrastructure for that that will likely also make voice cycling more easy. Hope that help, Kas.
2009/10/8 Kassen
Normally UGens are defined within the scope of a shred, this may be a sporked function or the "main shred", being the code in a file outside of any functions or classes.
When the shred a UGen was defined in exits the UGen will be disconnected from anything it connected to (I'm not sure everything that connects to the Ugen on the other end will also be disconected but that doesn't matter much here). Because of this the DAC will no longer poll it for new samples so it won't take any CPU anymore. This, in adition to calculation order, is the big advantage of the "pull through" model we use for the UGens.
Kas.
Cool! Just tried it out in an experiement - I wasn't aware of this behaviour. /Stefan -- Release me, insect, or I will destroy the Cosmos!
Stefan; Cool! Just tried it out in an experiement - I wasn't aware of this
behaviour.
I think it's Ge's doctoral thesis (on the site) that goes into most depth on this subject as well as the link between the code and the Ugen graph in general. If you didn't already i warmly recommend spending some time with it.
Kas.
Hey Kas:
After an experiment, I must take slight exception to one thing you said:
2009/10/8 Kassen
...When the shred a UGen was defined in exits the UGen will be disconnected from anything it connected to (I'm not sure everything that connects to the Ugen on the other end will also be disconected but that doesn't matter much here). Because of this the DAC will no longer poll it for new samples so it won't take any CPU anymore. This, in adition to calculation order, is the big advantage of the "pull through" model we use for the UGens.
Here's a simple program that defines a SinOsc and plays it in its own shred. After 3.5 seconds, the shred is killed (and we know that it has exited since its heartbeat / printing stops), yet the sound plays on. ============= fun void playit() { SinOsc s => dac; while (true) { <<< now / 1::second, "playing..." >>>; 1::second => now; } } spork ~ playit() @=> Shred @ _player; 3.5::second => now; <<< now / 1::second, "terminating shred..." >>>; _player.exit(); <<< now / 1::second, "waiting before exiting" >>>; 7.5::second => now; <<< now / 1::second, "exiting!" >>>; ============ produces bash-3.2$ chuck ~/Projects/Chuck/Tests/shred_exit.ck 0.000000 playing... 1.000000 playing... 2.000000 playing... 3.000000 playing... 3.500000 terminating shred... 3.500000 waiting before exiting 4.000000 playing... 11.000000 exiting! =========== [Digression: why the heck does playit() print out "4.00 playing..." when it was terminated at t=3.5? That may be because an exit() doesn't take effect until the next time a shred blocks. If so, that would be a nice thing to document.] I think what you meant was "When the shred a UGen was defined in exits *NORMALLY* the UGen will be disconnected from anything it connected to..." To test that, we simply let the player shred exit (without unchucking the dac): =========== fun void playit() { SinOsc s => dac; <<< now / 1::second, "playit playing" >>>; 1::second => now; <<< now / 1::second, "playit exiting" >>>; } spork ~ playit(); 7.5::second => now; <<< now / 1::second, "finished" >>>; =========== And yep, sure enough, the sound stops after 1 second. So the moral of the story: if you terminate a shred via exit(), it does NOT get the benefit of a standard shred clean-up. Perhaps "cleanup of a terminated shred" should be added to the someday-nice-to-have wish list. Cheers, - Rob
Rob; I think what you meant was "When the shred a UGen was defined in exits
*NORMALLY* the UGen will be disconnected from anything it connected to..." To test that, we simply let the player shred exit (without unchucking the dac):
I think I'd go as far as saying "When the shred a UGen was defined in exits the UGen *SHOULD* be disconnected from anything it connected to..." :-) In the last version, for example, it wasn't true in all cases, particularly not when multiple connections existed. There is at least one exception, which is Ugens that are static members of public classes (these are useful). I believe the general case might be that UGens that can no longer be addressed are disconnected but that's not true for something like this; repeat(3) { SinOsc s => dac; .3 => s.gain; Std.rand2f(200, 2000) => s.freq; } second => now; In that particular case you get three UGens that can't be addressed any more after this yet will run while the shred does. I'm not 100% sure that should be desired behaviour but there is no big issue with it either as hopefully people won't do this if that behaviour is unwanted.
And yep, sure enough, the sound stops after 1 second.
So the moral of the story: if you terminate a shred via exit(), it does NOT get the benefit of a standard shred clean-up.
Hmmmmm, right now I'm inclined to say that the moral of this story is "take care of the sense and the sounds will take care of themselves" and ChucK isn't taking care of the sense in all cases. This case, for example, seems downright nonsensical to me; this is a plain bug. In the Mini in both the new and the last version the sound keeps going even after the mother shred exited. So; the "slight exception" should not be with me but with ChucK and you found a bug. Congratulations! Yours, Kas.
So the moral of the story: if you terminate a shred via exit(), it does NOT get the benefit of a standard shred clean-up.
I'd like to briefly return to this. We're all human with the doubts and insecurities that come with that and the VM will come across at authoritative even while it's misbehaving. It can be hard when you're not that experienced with ChucK (yet) to say "I'm right and ChucK is wrong", I know I found that hard and would instead blame the issue on me not understanding the syntax, then keep it to myself I suspect a lot of found bugs go unreported because users blame themselves instead of blaming the VM. In case of doubt; report. It may be a bug (which can go on The List), it might be a issue with the docs (in which case it needs documentation) or you may misunderstand something (in which case you get to learn). Just wanted to point that out; the VM is wrong quite often. Yours, Kas.
I'm a bit intrigued by this behaviour, so I felt like testing what actually
happens when a shred exits.
Here's a little test program:
SinOsc @ sinosc;
fun void hello() {
SinOsc @ localSinOsc;
new SinOsc @=> localSinOsc;
localSinOsc @=> sinosc;
localSinOsc => dac;
1::second => now;
<<< "Mark 1b" >>>;
}
spork ~ hello();
<<< "Mark 1">>>;
0.5::second => now;
<<< "Mark 2">>>;
1000 => sinosc.freq;
1::second => now;
<<< "Mark 3">>>;
2000 => sinosc.freq;
1::second => now;
sinosc => dac;
<<< "Mark 4">>>;
while (true) {
1::day => now;
}
This will run without error. At Mark 1b it goes silent (the SinOsc creating
shred exits). At 3 it writes to that sinosc that has been stored outside the
shred without generating a NullPointerException, and at 4 it turns the
sinosc on again by chucking it to dac.
This means that UGens aren't necessarily destroyed by a shred that exits,
but it will diconnect them from the graph connected to dac.
Here's a tweaked version of the same program:
SinOsc @ sinosc;
fun void hello() {
SinOsc @ localSinOsc;
new SinOsc @=> localSinOsc;
localSinOsc @=> sinosc;
1::second => now;
<<< "Mark 1b" >>>;
}
spork ~ hello();
<<< "Mark 1">>>;
0.5::second => now;
<<< "Mark 2">>>;
sinosc => dac;
1::second => now;
<<< "Mark 3">>>;
2000 => sinosc.freq;
1::second => now;
sinosc => dac;
<<< "Mark 4">>>;
while (true) {
1::day => now;
}
Here the sinosc isn't chucked to dac in the shred that creates it, but it
still disconnects it. So the shred keeps track of what UGens have been
created inside it, and makes an effort to disconnect them when it exits.
I'm not going to argue right or wrong here - clearly there is an idea behind
disconnecting UGens in shreds that exits, and this is as good an
implementation of that as I can see. The alternatives would be to either
make the sinosc variable corrupt (point to a deallocated piece of memory),
or allow a shred to assign null to variables outside its scope, which
doesn't feel like a sensible thing to do.
Sorry for going a bit OT (mail threads can be forked), but I've always found
compiler theory fascinating in a hobbyist kind of way. :)
/Stefan
2009/10/9 Kassen
So the moral of the story: if you terminate a shred via exit(), it does NOT
get the benefit of a standard shred clean-up.
I'd like to briefly return to this.
We're all human with the doubts and insecurities that come with that and the VM will come across at authoritative even while it's misbehaving.
It can be hard when you're not that experienced with ChucK (yet) to say "I'm right and ChucK is wrong", I know I found that hard and would instead blame the issue on me not understanding the syntax, then keep it to myself
I suspect a lot of found bugs go unreported because users blame themselves instead of blaming the VM. In case of doubt; report. It may be a bug (which can go on The List), it might be a issue with the docs (in which case it needs documentation) or you may misunderstand something (in which case you get to learn).
Just wanted to point that out; the VM is wrong quite often.
Yours, Kas.
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
-- Release me, insect, or I will destroy the Cosmos!
participants (4)
-
Atte Andre Jensen
-
Kassen
-
Robert Poor
-
Stefan Blixt