Set Timeout

Timeout

hello all!

the following code works so far, though it might not be the most elegant one.

~play = Routine{

~bufnum = Pseq([0, Pseq((1…8).scramble), 9], inf).asStream;

10.do{

~sendSwitchTrigger.set(0);

0.yield;

}.yieldAndReset(reset: true);

};

)

~perform = {~play.next}.defer(rrand(0.0, 2.0));

I would like to include a „timeout“:

if the Routine is not triggered for 10 sec, it should be reset to the beginning: I tried out „.idle(10), but I could not figure out, where to put it.

thanks for any suggestions!

best

Rainer

Hi,

Note that the code in your message isn’t formatted as code (so it’s lost indentation, for one thing).

The tricky thing about a timeout is that it has to be possible to cancel it, when the next trigger comes in. At one point a few years ago, it struck me that a Routine can be used like that.

if(~timeout.notNil) { ~timeout.stop };
~timeout = Routine {
    10.wait;
    ~play.reset;
}.play;

You can put that in where a trigger comes in. Each trigger will cancel the previous timeout, and schedule a new one.

hjh

hello jamshark!
many thanks for your reply!
sorry for the non formatied code, but I am blind, and navigating and handling the forum page is unfortunately very tricky for me.

the trrigger that comes in is a MIDIDef, that goes into a Trig.
best
Rainer

Oh, then I apologize – will try to remember that next time.

In that case, I would make a function that does both ~play.next and the timeout routine.

~go = {
    ~play.next;
    if(~timeout.notNil) { ~timeout.stop };
    ~timeout = Routine {
        10.wait;
        ~play.reset;
    }.play;
};

Then your MIDIdef can call this function. (The basic idea here is that all the functionality can be accessed through code instructions, without MIDI or GUI, and the interface layer just uses these functions.)

hjh

hello jamshark!
no problem!
now I am aware of it and I will ask a sighted person to assist, if code is in my post.

great thanks for your detailled explanation!
best
Rainer

hello jamshark!
I am sorry again for the not formated code, but I put this answer directly via email.

here is my complete code:
it partially works, but the „play“ routine is not reset, and the .defer and timeout settings are ignored, when playing it with a sustain pedal.
I guess, the MIDIdef comes in in a wrong place?

(
~numSpeakers = 2; // sets the number of available speakers (currently 2, will finally be set to 4)

s.options.numOutputBusChannels = ~numSpeakers; // sets accordingly the number of output channels

s.options.memSize= 1000000000;
s.boot;
)
(
Buffer.freeAll;

~pS = PathName(thisProcess.nowExecutingPath).parentPath ++ “projectSamples”;
~samples = ();

s.waitForBoot(~samples = PathName(~pS).files.collect({|n| Buffer.readChannel(s, n.fullPath, 0, -1, [0])}), limit: 100);
)

~samples.at(0).play;

(
SynthDef(\player, {
var env, sig;

env = Env.asr(
0.01, 1.0, 0.01, -4
).ar(2, [gate.kr](http://gate.kr)(1));

sig = PlayBuf.ar(
1, [bufnum.kr](http://bufnum.kr)(0), [pbRate.kr](http://pbRate.kr)(1), loop: 0
);

sig = sig * env;
sig = sig * [amp.kr](http://amp.kr)(1.0);

OffsetOut.ar([out.kr](http://out.kr)(0), sig)

}).add;
)

~testPlayer = Synth(\player, [\amp, 1]);

~testPlayer.set(\gate, 0);

(
~t1 = TempoClock(60 / 60).permanent_(true);

~bufnum = Pseq([0, Pseq((1…8).scramble, 1), 9], 1).asStream;
~speakerSeq = Pxrand((0…(~numSpeakers - 1)), inf).asStream;

~previousPlayer = nil;
~sendSwitchTrigger = {
~previousPlayer !? {|p| p.set(\gate, 0) };
~previousPlayer = Synth(\player, [\bufnum, ~bufnum.next, \out, ~speakerSeq.next]);
};
)

(
MIDIIn.connectAll;
MIDIdef.freeAll;

MIDIdef.cc(\ped1, {

val, ccNum, chan, src| val.postln;
~sendSwitchTrigger.set(\trig, val)
},
[ccNum: 64, chan: 0, src: “DOREMiDi MPC-20-13C4”]
).permanent_(true);
)
)

(
~play = Routine{

~bufnum = Pseq([0, Pseq((1…8).scramble), 9], inf).asStream; // deffines the sequence of audio samples to be played

~speakerSeq = Pxrand((0…(~numSpeakers - 1)), inf).asStream; // defines the sequence of speakers

10.do{
~previousPlayer;
0.yield;
}.yieldAndReset(reset: true);
};
)

~perform = {~play.next}.defer(rrand(0.0, 2.0));

(
~go = {
~play.next;
if(~timeout.notNil) { ~timeout.stop };
~timeout = Routine {
10.wait;
~play.reset;
}.play;
};
)

great thanks for any advise!best
Rainer

One way to format code (not sure if it works by email, worth a try) is to put three backticks at the start and end of the code block.

```
... my code...
```

Anyway, my initial read of your code is that it’s overcomplicated, so the first thing to do would be to simplify as much as possible. I see ~sendSwitchTrigger, ~play and ~go – presumably any and all of these are involved in responding to a MIDI event. How about one function to prepare the data (where the data could come from a routine) and play a synth?

Also, the reason why the MIDIFunc isn’t doing anything is because ~sendSwitchTrigger is a function. To activate the function, you should use the .value method, but you’re using .set (which is appropriate for a Synth). But if you want the timeout logic, then the MIDIFunc should be calling ~go also. (If you have a timeout function but never call it, then you’re not going to get timeout behavior.)

I’m sorry I can’t be more specific right now – it’s a bit unclear to me what you’re trying to do.

hjh

Had another look. I think this is closer – at least, it corrects several problems.

(
~numSpeakers = 2; // sets the number of available speakers (currently 2, will finally be set to 4)

s.options.numOutputBusChannels = ~numSpeakers; // sets accordingly the number of output channels

// btw, you asked for 1 TB of realtime memory in the server.
// I'm surprised your server booted at all.
// So I've reduced this number by a factor of 10000.
// Or you could try 300000 or 500000.
s.options.memSize = 100000;
s.boot;
)
(
Buffer.freeAll;

~pS = PathName(thisProcess.nowExecutingPath).parentPath ++ “projectSamples”;
~samples = ();

s.waitForBoot(~samples = PathName(~pS).files.collect({|n| Buffer.readChannel(s, n.fullPath, 0, -1, [0])}), limit: 100);
)

~samples.at(0).play;

(
SynthDef(\player, {
	var env, sig;
	
	env = Env.asr(
		0.01, 1.0, 0.01, -4
	).ar(2, \gate.kr(1));
	
	sig = PlayBuf.ar(
		1, \bufnum.kr(0), \pbRate.kr(1), loop: 0
	);
	
	sig = sig * env;
	sig = sig * \amp.kr(1.0);
	
	OffsetOut.ar(\out.kr(0), sig)
	
}).add;
)

~testPlayer = Synth(\player, [\amp, 1]);

~testPlayer.set(\gate, 0);

~initBufSpeakerStreams = {
	~bufnum = Pseq([0, Pseq((1..8).scramble), 9], inf).asStream; // deffines the sequence of audio samples to be played
	
	~speakerSeq = Pxrand((0..(~numSpeakers - 1)), inf).asStream; // defines the sequence of speakers
};

~playOneSynth = {
	~synth !? { |p| p.set(\gate, 0) };
	~synth = Synth(\player, [\bufnum, ~bufnum.next, \out, ~speakerSeq.next]);
};

~doTimeout = { |time = 10|
	if(~timeout.notNil) { ~timeout.stop };
	~timeout = Routine {
		time.wait;
		~initBufSpeakerStreams.();
	}.play(SystemClock);
};

~playSynth = {
	~playOneSynth.();
	~doTimeout.();
};


~t1 = TempoClock(60 / 60).permanent_(true);
~initBufSpeakerStreams.();	

(
// note: MIDIFunc srcID cannot be a string
// so we're going to search the sources array for a matching MIDIEndPoint
var src;

MIDIIn.connectAll;
MIDIdef.freeAll;

src = MIDIClient.sources.detect { |src|
	src.device.containsi("DOREMiDi MPC-20-13C4")
};

if(src.notNil) { src = src.uid };

MIDIdef.cc(\ped1, { |val, ccNum, chan, src|
	val.postln;
	~playSynth.();
}, ccNum: 64, chan: 0, srcID: src).permanent_(true);
)

To fix the unformatted code, btw, I had to use this regexp search to fix the synth args:

  • Search: \[([A-Za-z0-9_]+).kr\]\(http://[A-Za-z0-9_]+.kr\)\(([0-9.]+)\)
  • Replace: \\\1.kr(\2)

hjh

Oh, I see one other thing: the argument to waitForBoot should be a function. If the action is not wrapped in { }, then it will run immediately, and it’s no longer wait for boot.

s.waitForBoot({ ~samples = PathName(~pS).files.collect({|n| Buffer.readChannel(s, n.fullPath, 0, -1, [0])}) }, limit: 100);

hjh

1 Like

hello jamshark!
great thanks for all your efford and your advise!
and I am sorry for my late answer!

I think I now understand the structure of the code you provided: when evaluating the block beginning with „( // comment
var src;
…) an error shows up, posting that „var“ is not expected, but „end of file“.
did I overlook something?
best from Austria
Rainer

That block of code executes correctly for me, so I suspect the problem is that it’s not finding the boundaries of the region.

This could be due to a syntax error within the region, or (a little more difficult to troubleshoot) a syntax error in the same document earlier than the region.

hjh

hello all!
I am controlling parameters of a SynthDef via MIDIdefs and linked MIDI controllers.
when a Synth is freed, and a new is started, the value for e.g. „pan“ does of corse start not at the recent fader poosition (last value), but it does also not start at the „default“ position set in:
var pan = [pan.kr](http://pan.kr)(0.0).linlin(0, 127, -1.0, 1.0).lag(0.2);
it always seam to start at the minimum value set above.
how can I get the actual fader position as start position into a new Synth?
thank you for any suggestions!
best
Rainer

Hi Rainer,

have you checked if the pan position works as you expect it when you
simply start one synth node as follows?
Synth.new(\rainersSynth);

Where do you use/set the code following code line?
var pan = [pan.kr](http://pan.kr)(0.0).linlin(0, 127, -1.0, 1.0).lag(0.2);

hi Peter!
thank you for your suggestions!
so to clarify:
the Synth works fine, when it is running, and does all the things it should do.
but
I was not able to call the Synth from „outside“ -
doing so prompted an error message: failure in Server n_node node 1000 not found.
after getting this message, the patch did not work anymore, and I hat to reboot and to start it again, and it again worked fine, exept that all parameters including the amplitude started at minimum value.
and this is also the case when changing a sample (Buffer).
the construction itself is started via MIDI controller by pressing a buttom, that is linked directly to a Synth.
the „var“ line is one statement in the var section of the related SynthDef.
there is also a MIDIdef that evaluates at each press the sequence of the Buffers to be played, and one for closing the gate of the ASR Env.
I did alot of testing only for finding the best min and max parameter bounderies, but there was no error during the tests at all, after the entire thing worked propperly.
best
Rainer

One thing here is that the minimum value is the default value: \pan.kr(0.0) means that the default value is 0. Then a range of 0 to 127 is mapped onto -1.0 to +1.0. The default 0 is at the lower boundary of the input range (the minimum), so the output value to which this is mapped must be the lower boundary of the output range = -1.0.

how can I get the actual fader position as start position into a new Synth?

Simple answer to initialize the pan value for every new Synth. If ~pan holds the current pan value (0 to 127, because that’s the input range you defined in your SynthDef):

~playOneSynth = {
	~synth !? { |p| p.set(\gate, 0) };
	~synth = Synth(\player, [
		bufnum: ~bufnum.next,
		out: ~speakerSeq.next,
		pan: ~pan
	]);
};

A question, though: is “pan” unique for every synth, or do all synths use the same pan value? If the latter, it can simplify the value-sharing to put the pan value onto a control bus, and tell all of the synths to read from that bus.

~panBus = Bus.control(s, 1);

MIDIdef.cc(\pan, { |val|
	~panBus.set(val);
}, ccNum: 10);

// and...
~playOneSynth = {
	~synth !? { |p| p.set(\gate, 0) };
	~synth = Synth(\player, [
		bufnum: ~bufnum.next,
		out: ~speakerSeq.next,
		pan: ~panBus.asMap
	]);
};

SC will not automagically apply a value to a parameter unless you explicitly do it in the code. (I use my own extension object, Voicer, to simplify this in my work.)

hjh

hello jamshark!
thank you for your reply!
I am not convinced, if I understood you correctly:
the Synths should start at a pan position = 0 = middle:
so the default value should be 0.0?
or should it be 64 as the MIDI value?

best
Rainer

There are two things here:

  • The value that you put into the synth control.
  • The value going into the panner.

x.linlin means: x should be between the first two numbers. In your code, that’s 0 to 127. And the result will be between the last 2 numbers, -1 to +1.

Input 0 maps onto -1 output – so if you want the output (pan value) to be 0, then the input value cannot be 0. So “the default value should be 0.0” – definitely not!

0.0 is exactly the midpoint between -1 and +1, so the input default should be halfway between 0 and 127 = 63.5.

hjh

hello jamshark70!
again many thanks!
it all works now so far.
best from Austria
Rainer

hello all!
so it works and the last part in the chain should be some sort of granulator:
sig = LFGauss.ar(~envdur, ~width, ~phase, loop: 1, doneAction: 1);
which also works -
but I want to apply a statistic function so that not all of the grains are played.
I thought of a gate, that is controlled by a random generator, but how can i do this?
thank you for any suggestions!
best
Rainer

A simple place to start for a statistic, or stochastic, function is a Bernoulli gate. A Bernoulli gate passes through a trigger or gate p percent of the time – that is, the inputs are a sequence of triggers and a probability (which is easiest to express as 0.0 to 1.0).

To implement it, you need a triggered random number spanning the full range over which the probability exists (0.0 to 1.0): TRand.ar(0, 1, trig) (or TRand.kr).

Then, if p is the probability, the range from 0 to p spans the “good” proportion of the unit range: TRand.ar(0, 1, trig) <= prob or TRand.ar(0, 0.9999999, trig) < prob.

Then you multiply this by the trigger or gate. When the comparison is false, its value is zero, so multiplying by it will suppress the trigger.

The catch with LFGauss is that you don’t exactly have a trigger as an input. Then I realized, when width is less than 0.267 or so, the lowest value of LFGauss is less than 0.001. (I found it empirically by testing, even though the help file talks about calculating it.)

Triggers in SuperCollider are defined as a transition from zero or negative, to positive. LFGauss is always positive, but if we subtract a small amount then it will be negative at the bottom.

This demo controls the envelope width by MouseY, and probability by MouseX.

(
a = {
	var prob = MouseX.kr(0, 1, 0);
	var width = MouseY.kr(0.01, 0.26, 0);
	var env = LFGauss.ar(0.125, width);
	// "- 0.001" guarantees a trigger per envelope cycle
	var bernoulliMask = TRand.ar(0.0, 1.0, env - 0.001) <= prob;
	var sig = SinOsc.ar(220, 0, 0.1);
	(sig * (env * bernoulliMask)).dup
}.play;
)

a.free;

hjh

PS I recall that you’re visually impaired, and haven’t paid attention to code block formating for that reason. I think in the long run, this will make it more difficult for others to interact with you on the forum (for instance, if they have to fix your code before trying to run it, fewer people will volunteer to help). So I’d like to point out that you can format code blocks by typing without using the mouse.

If it’s a short bit of code in a text paragraph – inline – you can enclose the code in single backticks.

Single backtick example: The class `SinOsc` is a sine oscillator.

For a larger block, or even a single line of code by itself, outside of a paragraph, start with three backticks on their own line, then write the code, and then close with three more backticks.

```
// those 3 backticks introduce a code block
a = { "This is more code" }.value;
b = "And we will close this block with three more backticks";
```

Hope this helps –