Error selecting program in block

You should add a cookbook-section to the documentation on how to do typical use cases properly.

In an experimentation-friendly enviroment like sc people only read documentation when something goes wrong and there is a lot of cargo-cult programming where you copy some snippet from somewhere that seems to work and so you assume that this is the way to do it when in reality it is just a hack that works for reasons you don’t even understand.

But the more I understand about the problem the more I come to appreciate that the interface is actually quite well thought out.

Actually, VSTPluginController.schelp has a bunch of examples for typical use cases at the end.

The second sentence of the help file literally says:

Have a look at the examples at the bottom!

I guess I should add a link to the section… It would stick out visually and also make it easier to navigate.

In an experimentation-friendly enviroment like sc people only read documentation when something goes wrong and there is a lot of cargo-cult programming where you copy some snippet from somewhere that seems to work and so you assume that this is the way to do it when in reality it is just a hack that works for reasons you don’t even understand.

Yes, that is the sad reality.

Not this time – I took a shortcut without checking. That’s on me.

hjh

1 Like

So what happens if the open fails in the above example with the CondVar?

Does cv,wait then block forever?

So what happens if the open fails in the above example with the CondVar?

Does cv,wait then block forever?

I was afraid you would ask this :slight_smile: Yes, in this case it would block forever. (Note that you would still get an error message in the console, so you at least know that something has been going wrong.)

Here’s the version with error handling:

fork {
    var cv = CondVar(), done = false;
    ~fx = VSTPluginController(~synth).open("foo", action: { done = true; cv.signalOne });
    cv.wait { done }; // note the boolean test!
    if (~fx.isOpen) {
        // now you can use the plugin
        ~fx.editor;
        ~fx.loadPreset("baz");
    } {
        // error handling
    }
}

Or with the older Condition (which does not require to check a separate variable):

fork {
    var cond = Condition(true);
    ~fx = VSTPluginController(~synth).open("foo", action: { cond.signal });
    cond.wait;
    if (~fx.isOpen) {
        // now you can use the plugin
        ~fx.editor;
        ~fx.loadPreset("baz");
    } {
        // error handling
    }
}

That looks like a good pattern, I’ll adopt that.

Is ~fx still useful in the error handling branch (maybe to get more details on the error condition) or is at this point all you know that the open was not successful and ~fx can no longer be used?

You wouldn’t get any details, no. The reason for the failure is posted to the console though.

and ~fx can no longer be used?

You can try another plugin :slight_smile:

I have to admit that I personally don’t really do any error handling when opening plugins. It’s not like plugins will just refuse to open randomly. Once the plugin has been probed successfully, I can just assume it will always work. If something goes wrong for some unexpected reason, the console will tell me anyway.

This is trickier than expected…

Why is the following not working as expected?
I always end up with the “NOT open” branch…

(
fork {
    var cond = Condition(true);
	SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2)) }).add;
	s.sync;

	~pt = VSTPluginController(Synth(\mysdef)).open("Pianoteq 8.so", action: { cond.signal });
    cond.wait;
    if (~pt.isOpen) {
		"open".postln;
		~pt.program_(26);
    } {
        "NOT open".postln;
   }
}

)

UPDATE: If I replace the Condition with a CondVar as above it works, so the two versions as given above do not seem to be equivalent.

Could be that you can’t initialize the Condition with true and instead set test to true in the action function. TBH, I always forget how Condition works…

Personally I tend to use CondVar because the semantics are more clear to me. Just don’t forget the boolean check in the call to wait!

I have tried to do away with the boolean check in wait as I don’t understand why it is needed - but it definitely seems to be necessary - can you explain why that is?

So why is it not sufficient to have the action signal the condvar which should wake up the thread sitting on wait (without a predicate) which can then check if the plugin is open. But when I try it like that it seems wait block forever - but why is that?

This seems to work:

(
fork {
    var cond = Condition.new();
	SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2)) }).add;
	s.sync;

	~pt = VSTPluginController(Synth(\mysdef)).open("Pianoteq 8.so", action: { cond.test = true; cond.signal() });
    cond.wait;
    if (~pt.isOpen) {
		"open".postln;
		~pt.program_(26);
    } {
        "NOT open".postln;
   }
}
)

You are talking about CondVar and not Condition, right? Classic condition variables always need a predicate because they do not ‘know’ that they have been signalled. If the async operation is very fast, the callback may be scheduled before the main routine calls wait. In this case, signalOne has no effect because the routine isn’t waiting yet. Without the predicate, the subsequent wait call would block forever because nobody will signal it. With the predicate, it will just return immediately without waiting.

Yeah, that’s how Condition is supposed to be used. I got a bit confused there.

With CondVar, you can dispense with the variable I believe:

(
fork {
    var cv = CondVar();
	
	~synth = Synth(\vst);
	
    ~fx = VSTPluginController(~synth).open("foo", action: { cv.signalOne });
	
	// also, I'll add a timeout
	// so that failure doesn't block the thread forever
	cv.waitFor(5) { ~fx.isOpen };

	if (~fx.isOpen) {
        // now you can use the plugin
		~fx.editor;
		~fx.loadPreset("baz");
    } {
        // error handling
    }
}
)

And CondVar has a timeout feature, which allows you to continue in the thread in case of failure.

The waitFor timeout is a good reason to prefer CondVar over Condition.

hjh

FWIW, that’s what I originally had in Error selecting program in block - #11 by Spacechild1 before @flower correctly pointed out the missing error handling.

The 5 second timeout is a neat compromise.

I think, though, that without a timeout, it’s impossible to advance beyond the wait-with-predicate e.g. cv.wait { done } until the predicate is true…? So the error handling branch would never be reached.

CondVar can:

  • wait without a predicate – always advances when signaled, even if the async op was unsuccessful;
  • wait with a predicate, and no timeout – no way past the barrier except success (failure means the thread blocks forever);
  • wait with a predicate and timeout – the thread can advance right away on success, or after the timeout on failure.

hjh

I think, though, that without a timeout, it’s impossible to advance beyond the wait-with-predicate e.g. cv.wait { done } until the predicate is true…?

In my example in Error selecting program in block - #16 by Spacechild1 done will always be set by the action function and wait will eventually return (assuming that OSC messages are not dropped).

As I explained in Error selecting program in block - #24 by Spacechild1, CondVar always requires a predicate. The predicate can be external, though:

while { done.not } { cv.wait };

is equivalent to

c.wait { done };

2 posts were split to a new topic: Async ops (again)

That indeed looks quite elegant.

Is the lack of a “proper async programming model” a general point of pain when you do larger projects?

I don’t understand a word but you seem to know your stuff…