Fork inside Ndef

When I insert a fork:


(
fork{
	loop{
		play{
			SinOsc.ar(exprand(100,1000))*
			XLine.kr(0.1,0.001,2,1,0,2)};
		0.5.wait;
	}
}
)

inside a Ndef:


(
Ndef(\asdf, {
	fork{
		loop{
			play{
				SinOsc.ar(exprand(100,1000))*
				XLine.kr(0.1,0.001,2,1,0,2)};
			0.5.wait;
		}
	}
})
)

I am getting two unexpected behaviors:

  • the routine begins with two simultaneous SinOscs and then continues as expected,

  • In the post window I am getting the message ERROR: Message 'children' not understood.

Why is this the case and how can these issues be resolved?

fork{} is a shortcut for Routine{}.play. To be clear, you can put a Routine in a NodeProxy source and it will start playing immediately. However, if you fork the Routine and then also put it in a NodeProxy, it will then play 2 copies of the Routine, which is why you are getting the first unexpected behavior. So a first solution to this is to simply put the Routine in the NodeProxy without forking it:

(
Ndef(\asdf, 
    Routine{
        loop{
            play{
                SinOsc.ar(exprand(100,1000))*
                XLine.kr(0.1,0.001,2,1,0,2)
            };
            0.5.wait;
        }
    }
).play;
)

Ndef(\asdf).filter(10, (_ * LFPulse.kr(8))); // has no effect
Ndef(\asdf).stop(fadeTime: 10); // has no effect

This plays only one copy of the Routine as expected, but it is not an ideal way to do things because if you look at the Node Tree you will see that the synths are not playing in the Ndef’s group, nor on the Ndef’s private bus, so you have no connection to the monitor or other objects associated with the Ndef. That’s why adding a filter and stopping the Ndef monitor have no effect. You can fix this by specifying a target and bus in the function::play method:

Ndef.clear;

(
Ndef(\asdf, 
    Routine{
        loop{
            {
                SinOsc.ar(exprand(100,1000))*
                XLine.kr(0.1,0.001,2,1,0,2)
            }.play(Ndef(\asdf).group, Ndef(\asdf).bus);
            0.5.wait;
        }
    }
).play;
)

Ndef(\asdf).filter(10, (_ * LFPulse.kr(8))); // works
Ndef(\asdf).stop(fadeTime: 10); // works

However, I think this is kind of a clunky approach, so I generally avoid using Routines inside Ndefs. One alternative is to use the \setsrc NodeProxy role:

Ndef.clear;

(
Ndef(\asdf, \setsrc -> Pbind(
    \dur, 0.5,
    \source, {
        SinOsc.ar(exprand(100,1000)) *
        XLine.kr(0.1,0.001,2,1,0,2)
    }
)).play;
)

Or perhaps create a SynthDef and then use a Pbind inside the Ndef:

Ndef.clear;

// need to use the ExpRand Ugen here, not the exprand method
{SinOsc.ar(ExpRand(100,1000)) * XLine.kr(0.1,0.001,2,1,0,2)}.asSynthDef(name: \bbb).add;

(
Ndef(\asdf, Pbind(
    \instrument, \bbb,
    \dur, 0.5
)).play;
)
2 Likes

Great explanation, thanks!