Waiting on events in do loops

Hi,

I’m trying to achieve a kind of flexible/interactive-looper mechanism where a loop is set of pre-defined Synths and Effects triggered at pre-defined times. The “flexible/interactive” part is achieved through some kind of pattern or human intervention that would affect the normal behavior from to time.

The question is about being notified by the currently playing loop that she is done, before starting it again. A kind of ‘Done’ event.

This is how far am I now:
This is a basic example, with only 2 synths. In a real case, the loop could be build upon several unrelated Synth with synthesis, wav files, … A kind of collage.

(
// A basic Synth
SynthDef(\basic,
	{ |out=1, freq=440, amp=0.1, form=0, gate=1, distort=0|
		var sig=SinOsc.ar(freq*(1+LFNoise1.kr(500,distort))).pow(form.linexp(0,1,1,20,nil));
		var env=EnvGen.kr(Env.asr(0.1,amp,0.3), gate,doneAction: 2);
		Out.ar(out,sig*env);
	},
	variants: (bronze: [freq: 220, form: 0.5], silver:[freq: 232, form: -0.5])
).add;
)

// The loop
(
~loop= {
	| i, distort = 0, start2=4, duration2=4 |

	var osc = Array.fill(2,nil);

	Post << "Play " <<< (i+1) << "e occurence" << Char.nl;

	t = TempoClock(2);

	t.schedAbs(0, { arg beat, sec;
		osc[0]=Synth('basic.bronze');
		nil; // don't repeat
	});
	
	t.schedAbs(start2, { arg beat, sec;
		osc[1]=Synth.new('basic.silver',args:[\out,0, \distort: distort]);
		nil; // don't repeat
	});
	
	t.schedAbs(5, { arg beat, sec;
		osc[0].release;
		nil; // don't repeat
	});
	
	t.schedAbs(start2+duration2, { arg beat, sec;
		osc[1].release;
		nil; // don't repeat
	});
}
)

// Paying the loop in repetitive mode
(
~distort=Array.series(4,0,0.1);
{
	5.do{arg j; 
		var s2=max(0,4+j.rand2);
		var d2=max(4+j.rand2,0.1);
		Post << s2 <<< ' --> ' <<< (s2+d2) << Char.nl;
		~loop.value(j, 
			distort: ~distort.wrapAt(j).rand,
			start2: s2,
			duration2: d2
		);
		 
		4.0.wait; 
	}
}.fork;
) 

Instead of this hardcoded 4.0.wait, I would like to take into account the duration of the loop. If it had be only about patterns this could be computed. But as my objective is to have human intervention, the duration loop cannot be computed.

So I’d like to replace that 4.0.wait by a waitUntilReady.
And to have in my loop definition, something like sendReadyEvent

t.schedAbs(start2+duration2, { arg beat, sec;
		osc[1].release;
		sendReadyEvent;
		nil; // don't repeat
	});

What would be the best way to do this ?

PS: any comment on the rest of my code is also welcome

Thanks,

I got it.
I’m using a Condition with hang and unhang.

(
~loop= {
	| i, distort = 0, start2=4, duration2=4, ready=nil |

	var osc = Array.fill(2,nil);

	Post << "Play " <<< (i+1) << "e occurence" << Char.nl;

	t = TempoClock(2);

	t.schedAbs(start2+duration2, { arg beat, sec;
		osc[1].release;
		if (ready.notNil, { ready.unhang}); // ready for next loop. tell it
		nil; // don't repeat
	});
}
)


(
~distort=Array.series(4,0,0.1);
{
	5.do{arg j; 
		var s2=max(0,4+j.rand2);
		var d2=max(4+j.rand2,0.1);
		var ready=Condition(false);
		Post << s2 <<< ' --> ' <<< (s2+d2) << Char.nl;
		~loop.value(j, 
			distort: ~distort.wrapAt(j).rand,
			start2: s2,
			duration2: d2,
			ready:ready;
		);
	 
		ready.hang; // waiting until the currently loop tells me it is ready.
	}
}.fork;
) 

My solution works because we are in a Routine.
What if same behaviour is expected in some other type of code ?

Example:

~can_release=Condition(true); // By default the normal release can be done
~can_release.signal; // mandatory of propagating the condition

(
~loop= {
	t = TempoClock(2);
	...

	t.schedAbs(5, { arg beat, sec;
		~can_release.wait; // KO , causes a "Message 'wait' not understood." Because occurs outside of a Routine ?
		osc[0].release;
		nil; // don't repeat
	});
}
)

For now, I’ve written it this way

	t.schedAbs(5, { arg beat, sec;
		//~can_release.wait; // KO
		~manual_gate.if(
			{ 1 } // don't do the release now, let's wait 1 beat
			, {
				osc[0].release;
				nil; // don't repeat
		});
	});

I don’t like it, because it introduces some kind of quantization.

Are they other ways of doing this ?

A simple workaround would be to fork a Routine:

t.schedAbs(2, { arg beat, sec;
	fork {
		~can_release.wait; // now we are inside a Routine: it's OK to wait
		"release".postln;
		nil; // don't repeat
	}
});

The idea is that if you want to wait on a condition, you need to be in a Routine. However, depending on how your user interaction works, there could be other solutions… kind of hard to say without knowing more about your system.

Thanks. Basically, I’m trying to give the user the possibility to put the pattern/loop into a suspend mode, where a number of event are repeated until the user instructs the release.
In my example, the suspend mode is limited to “let all active synths active”. And the release limited to “release everything which is not already released”.
In a more complex form, the suspend mode would be “repeat all the sustain events” , and the release would be “trigger/reschedule all the events from the release part”.

I think elgiano’s solution is probably the most elegant here.

Or… you’re using sched to pass a little time before waiting on the condition. You could fold that into the Routine.

fork {
    2.wait;
    ~can_release.wait;
   ...
};

Seems conceptually a little cleaner to use just the one mechanism.

wait, like yield, depends on the ability to pause an execution flow and resume from the same place. The only way to do this in sclang is in a Routine. There’s no exception to that rule.

So if you need this capability in some other code, then the other code must create a Routine.

hjh

1 Like