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.
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!