[chuck-users] Why do child shreds get killed?

Robin Davies robin.davies at quest.com
Tue Sep 6 12:53:21 EDT 2005


Currently, if a shred terminates, all of its child shreds get terminated
as well.

 

This raises the following interesting question: why? 

 

Honestly, from a user's perspective, I can't think of a compelling use
of this feature. Whether there are internal implementation issues that
motivate this feature or not is a separate question, I guess; but I
can't really think of a good reason to terminate child shreds from an
implementation perspective, either. (Unless you're relying on shred
termination to garbage collect).

 

On the other hand, I can think of very compelling reasons why shreds
should not outlive their parents. 

 

I've been bitten by this several times - by shreds silently being
terminated because parent shreds have terminated.

 

Here's my compelling use case for allowing shreds to outlive their
parents.

 

I spent the weekend playing with chuck. With audio languages (CSound,
SAOL, Music-N, &c), one of the constant challenges is how to provide
clean and modular integration between expensive control-rate operations,
fast audio-rate operations, and external events.

 

Having had exposure to MusicN based languages that provide control-rate
processing, I thought I'd try using the control concept in chuck. 

 

Here's the theory: write an IInstrument abstract class that provides an
abstract Tick() method which gets called at control-rate. Output of the
instrument is placed on the Instrument.Out ugen so that instruments can
then be routed onto an external output. Implementations of IInstrument
then call (for example)  StartTick(16::samp), which causes the Tick
method to be called every 16 samples on a separate shred. This works
very well. The end result: an AnalogSynth class, implementing
IInstrument, that provides two two control-rate lfos, two control-rate
envelopes, one band-limited oscillator ugen (private build coming to a
Chuck near you soon) and a filter ugen. AnalogSynth runs about 12 voices
in realtime on my lowly 800Mhz home machine, without any real attention
to optimization! So far so good.  The purpose of the Tick() processing
is,of course, to run the LFOs and ADSrs at reduced rate (every 16
samples), and then perform all of the expensive routing of lfos and
ADSrs outputs onto UGens at something less than audio rate. 

 

Well. It does work very well, as long as I drive the IInstrument from
midi or OSC input. It fails spectacularly if notes are triggered from
temporary shreds when writing scores in Chuck. Consider the following: 

 

function void PlayNote(IInstrument instr, float midNote, dur length)

{

            instr.NoteOn(midiNote,1.0);

            length => now;

            instr.NoteOff(midiNote);

}

 

spork ~ PlayNote(analogSynth, Midi.C4, 1.5::beat);

 

This fails, spectacularly. Internally, the Tick shred gets started up,
but it shuts down as soon as the PlayNote shred terminates (causing the
note to get stuck).

 

In retrospect, I guess the answer is to create a tick shred at the time
the instrument is created, and then use events to synchronize the tick
shred. But that's just so... so... inconvenient. Or another approach:
make PlayNote wait forever, after the note off. But that's just so...
so.... ugly. Hmm. Or maybe a WaitForAllChildThreadsToDie() function. 

 

The event solution also runs into significant problems with controlling
the first tick after startup. Consider: An IInstrument receives a
note-on event. It initializes all of it's control rate state variables
(reseting ADSRs, for example). Now it needs to start the tick shred up.
So it signals an event. The question is this: will the tick shred run in
the current cycle or not? If it doesn't then StartTick() should call
IInstrument.Tick(). But whether the shred ran in the current thread or
not is non-deterministic. It depends on whether the shreduler ran the
current thread or the tick thread first for the current value of Now.
Even worse. The tick shred can no longer perform "16::samp => now",
because the noteon event may have arrived at some non-modulo-8 mutiple
of now::samp. So this approach requires complicated and expensive
handshaking between the Instrument and its tick thread to get everything
lined up and operating deterministically. (<thinking>... this is doable,
and probably the way I'll go)

 

Or another use-case for the same:

 

function void PlayTheWholeSong() 

{

            Spork ~ PlayBassMotif1();

            Spork ~ PlayMelody1();

            12::beat => now;

            Spork ~ PlayBassMotif1();

            Spork ~ PlayMelody2();

 

            // oops. No more bass motif or melody shred.

} 

 

The feature request; maybe a way to let shreds re-assign their parent
shred to the root shred. That would be nice. Or maybe just don't
terminate child shreds at all.

 

 

Robin Davies
Lead Software Developer
Quest Web Reports
Quest Software
613.270.1569

 

Identity Management for the Windows Enterprise
<http://wm.quest.com/reg/marketing/landing/identitymanagement/> : See
how Quest helps you leverage your existing investment to solve the
identity management challenge!

 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.cs.princeton.edu/pipermail/chuck-users/attachments/20050906/9a4e79ff/attachment.htm


More information about the chuck-users mailing list