Error selecting program in block

Hi,

can someone please explain why this results in an error when run as a block (via control-enter in the ide) yet works fine when each statement is run on it’s own via shift-enter?

What is the difference?

(
SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2));}).add;
~pt = VSTPluginController(Synth(\mysdef));
~pt.open(“Pianoteq 8.so”, editor: true);
~pt.program_(26);
)

When I run it as a block this is the error:

CALL STACK: MethodError:reportError arg this = <instance of MethodError> Nil:handleError arg this = nil arg error = <instance of MethodError> Thread:handleError arg this = <instance of Thread> arg error = <instance of MethodError> Object:throw arg this = <instance of MethodError> VSTPluginController:program_ arg this = <instance of VSTPluginController> arg number = 26 Interpreter:interpretPrintCmdLine arg this = <instance of Interpreter> var res = nil var func = <instance of Function> var code = "( SynthDef(\mysdef, { |out..." var doc = nil var ideClass = <instance of Meta_ScIDE> Process:interpretPrintCmdLine arg this = <instance of Main> ^^ The preceding error dump is for ERROR: program number 26 out of range RECEIVER: a VSTPluginController

First suggestion – please use

```
code tags
```

for code. Otherwise (for instance) quote marks become curly quotes, which will generate syntax errors.

Now, the real problem. Every one of these lines (except the VSTPluginController line) is an asynchronous operation. It’s necessary to wait for each one to complete before moving on to the next step, but you’re just running them all immediately.

fork {
	SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2)); }).add;
	s.sync;  // must wait for synthdef to be ready
	~pt = VSTPluginController(Synth(\mysdef));
	s.sync;  // *maybe* not necessary to wait here, but doesn't hurt
	~pt.open("Pianoteq 8.so", editor: true);
	s.sync;  // must wait for plugin to load
	~pt.program_(26);
}

hjh

Unfortunately, as I understand it and also based on my own experience (plus I think a post by @Spacechild1 a while ago), there is no way of knowing when the VSTPluginController will be ready to load the editor, so in the above example the editor will not show up (not on my OSX system anyways). The work around is to set an explicit wait time before opening the editor - there might be a more elegant way to this, if it exist I would be keen to know:

fork {
	SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2)); }).add;
	s.sync;  // must wait for synthdef to be ready
	~pt = VSTPluginController(Synth(\mysdef));
	s.sync;  // *maybe* not necessary to wait here, but doesn't hurt
	~pt.open("Pianoteq 8.so");
	0.3.wait;
	~pt.editor;
}

My understanding is that ~pt.open(..., editor: true) is not meant to open the editor – it is only meant to enable the plugin’s native GUI (rather than a flat list of parameters). So the above example should not open the GUI.

hjh

@jamshark70

s.sync; // *maybe* not necessary to wait here, but doesn't hurt

Yes, not necessary indeed. In fact, you can even do the following:

~pt = VSTPluginController(Synth(\mysdef)).open("Pianoteq 8.so");

(Side note: editor: true is the default and therefore redundant.)

You can see this pattern a few times in the docs.

~pt.open("Pianoteq 8.so", editor: true);
s.sync;  // must wait for plugin to load

Don’t use s.sync, use an action function! The documentation explicit says:

NOTE: You must not use Server: -sync to wait for completion! If you prefer to write sequential code instead of callbacks, you may use a Condition resp. CondVar and signal it from the action function.

So the correct solution would be the following:

fork {
    SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2)); }).add;
    s.sync;  // must wait for synthdef to be ready
    ~pt = VSTPluginController(Synth(\mysdef));
    ~pt.open("Pianoteq 8.so", action: { |x| x.program_(26) });
}

Or as a one-liner:

fork {
    SynthDef(\mysdef, { |out = 0| Out.ar(0, VSTPlugin.ar(numOut: 2)); }).add;
    s.sync;  // must wait for synthdef to be ready
    ~pt = VSTPluginController(Synth(\mysdef)).open("Pianoteq 8.so", action: { |x| x.program_(26) });
}
1 Like

Not true! See my post above. TBH, I find it a bit frustrating that people would assume that I wouldn’t provide such basic functionality… When in doubt, read the docs!

The work around is to set an explicit wait time before opening the editor

No, just no!

there might be a more elegant way to this, if it exist I would be keen to know

See above :slight_smile:

Writing docs is hard… On the one hand, I think the documentation for VSTPlugin is quite extensive, but I also get that it can be a bit overwhelming and most people just don’t read it. I guess I need to revise the “Introduction” section to include the use of the action function. Now, if people wouldn’t even read that, I’ll just give up :smiley:

Generally, suggestions for improving the documentation are very welcome. It can be hard as an author, who knows the software inside out, to write good documentation for beginners.

No need for the aggression, I stand corrected and I did read the docs several times but either forgot or missed this case. And btw, I am very happy about the VSTPlugin and use it extensively, thanks for the hard work.

2 Likes

No aggression, just frustration. And not about you in particular, but about writing docs in general. Sorry if I was a bit harsh.

As I said, the mistake is on me, I should already show this usage in the introduction and not bury it deep down in the method documentation… When even a power user like @jamshark70 gets the usage wrong, the problem is really on my end.

And btw, I am very happy about the VSTPlugin and use it extensively, thanks for the hard work.

Thanks for the kind words! I’m glad people find it useful.

1 Like

Every one of these lines (except the VSTPluginController line) is an asynchronous operation.

Aha, I was not aware of that. So is it just an accident (because you are much slower that way) that it works when you run the lines individually or does the ide insert implicit syncs?

What now remains unclear to me is why you ever would want to run “open” without supplying an action as then you never really know when the plugin is ready and everything becomes a timing lottery.

So a less user-error-prone interface could be that you either supply an action or if you don’t do that open only returns when the plugin is ready to use. Or is there a use-case for initiating the loading of a plugin and not caring about when it is ready?

Finally what is the use-case for “open” with “editor: false”? Does that make the loading of the plugin more efficient when you are sure you never want to use the editor?

The former. As I already said above, I should probably change the documentation and use proper action functions instead of line-per-line style even in the introduction.

So a less user-error-prone interface could be that you either supply an action or if you don’t do that open only returns when the plugin is ready to use.

I’ve followed the general pattern of the SC Class Library which is to provide action functions. See, for example, Buffer.read.

At least with Buffer.read you can just use s.sync instead (which is problematic for a bunch of other reasons), but with VSTPluginController.open this is not a (reliable) option because the method might take more than one Server roundtrip.

Personally, I would have preferred it if async methods would return a Promise object, like in JS, ideally with a nice async/await syntax, but the SC Class Library has been designed long before async programming became mainstream, so at the moment we are stuck with callback hell :face_with_diagonal_mouth:

Finally what is the use-case for “open” with “editor: false”? Does that make the loading of the plugin more efficient when you are sure you never want to use the editor?

Exactly.

Or is there a use-case for initiating the loading of a plugin and not caring about when it is ready?

I can think of at least two use cases:

  1. you are happy with the default settings of the plugin
  2. all parameters are automated with UGen inputs
1 Like

BTW, you can always “convert” callbacks into sequential style with the help of CondVar:

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

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.