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: See how Quest helps you leverage your existing investment to solve the identity management challenge!