Host vs. Plugin Responsibilities

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

As I travel deeper into the rabbit hole of plugin development, I'm discovering a couple of issues that make it very difficult as a plugin developer to work around.

First off, there are a few hosts out there that are very bad citizens--they hijack your handlers, steal mouse or keyboard inputs, etc. I now have Windows-only code littering my "platform-free" code to handle just trying to get a return key. If all the hosts played nice, I'd just be passing along all the keystrokes I'm not using, like it's supposed to work.

Another matter I just discovered is that if your host is handling multi-core support and your plugin is handling multi-core support, they don't really get along very well.

So, my question (and challenge) is, who should be handling what and can we all finally agree? Are there other issues you've all seen that I haven't run into yet? I probably sound like I'm whinging (I am :lol: ) but I'd like this to be a serious discussion so I can understand the why's of some decisions. :help:
I started on Logic 5 with a PowerBook G4 550Mhz. I now have a MacBook Air M1 and it's ~165x faster! So, why is my music not proportionally better? :(

Post

Basically it's your job to test every possible scenario and to answer customer support emails regarding the problems and that's all there is to it as "responsibility assignment".
~stratum~

Post

syntonica wrote: Wed Dec 05, 2018 12:41 am First off, there are a few hosts out there that are very bad citizens--they hijack your handlers, steal mouse or keyboard inputs, etc. I now have Windows-only code littering my "platform-free" code to handle just trying to get a return key. If all the hosts played nice, I'd just be passing along all the keystrokes I'm not using, like it's supposed to work.
This is largely a problem with the Win32 event model really, since (unlike every modern toolkit) it doesn't "bubble up" anything. The host can either intercept the message in it's message loop (which is how shortcuts usually work) or it can pass it on to your plugin, but it can't really ask in any standard way whether or not your plugin wants the event. If your plugin doesn't handle the event, then DefWindowProc generally just ignores it. If you want something more complicated, it's time to install some hooks.

That said, you probably shouldn't litter your "platform-free" code with any of this. Instead you should define an interface for anything and everything platform dependent that your main code relies on, then implement all of it in separate platform specific wrappers (which can then do whatever black magic is required to get things to work). When done properly, your main code never even needs to include any platform specific headers.

Post

mystran wrote: Thu Dec 06, 2018 10:49 am
syntonica wrote: Wed Dec 05, 2018 12:41 am First off, there are a few hosts out there that are very bad citizens--they hijack your handlers, steal mouse or keyboard inputs, etc. I now have Windows-only code littering my "platform-free" code to handle just trying to get a return key. If all the hosts played nice, I'd just be passing along all the keystrokes I'm not using, like it's supposed to work.
This is largely a problem with the Win32 event model really, since (unlike every modern toolkit) it doesn't "bubble up" anything. The host can either intercept the message in it's message loop (which is how shortcuts usually work) or it can pass it on to your plugin, but it can't really ask in any standard way whether or not your plugin wants the event. If your plugin doesn't handle the event, then DefWindowProc generally just ignores it. If you want something more complicated, it's time to install some hooks.

That said, you probably shouldn't litter your "platform-free" code with any of this. Instead you should define an interface for anything and everything platform dependent that your main code relies on, then implement all of it in separate platform specific wrappers (which can then do whatever black magic is required to get things to work). When done properly, your main code never even needs to include any platform specific headers.
On the mac side, the messages just sort of bubble themselves up the chain automatically--you use it and kill it, or pass it along. I thought the DefWinProc took the message and passed it up the food chain in the same fashion. Does it just throw the unwanted message into the bit bucket?!?. Can I just pass unused messages back to the host instead? Or, will they just pass them back to me? I know Win32 (well, it goes back to Win16) is old, but they haven't made any improvements under the hood for more consistent handling?

Regarding host behavior, is this a deliberate attempt to ensure a consistent user experience, or more a case of, Well, this is how we've always done it... ho hum...? I'm trying to understand as a Johnny-come-lately to the Windows programming world.

My GUI handler is, by necessity, mixed code, handling the message packets put together by the pure platform-based classes (c++ for Windows, Objective-C for Mac). I can probably factor the code off to some other place, but it's just the current kludge until I can find an even better solution. I prefer simplicity and harmony. I do not want host-specific code at all. I've even managed to dump the Reaper code on the Mac side in regards to Cocoa vs Carbon.
I started on Logic 5 with a PowerBook G4 550Mhz. I now have a MacBook Air M1 and it's ~165x faster! So, why is my music not proportionally better? :(

Post

Like I said, just about every modern toolkit can bubble events up the chain, but Win32 is not one of them. You can certainly implement such functionality in your application, but since it's not standard it's unreasonable to expect a random host to play along.

So ignoring custom hacks, the host can either choose to handle a given event or pass it to the plugin with the keyboard focus, but it has to make this decision without asking the plugin. You can't reliably pass the event back to the host either, because you have no idea where to send that event; there's no obligation for a given host to handle keyboard events in any particular window (and they aren't going to bubble up in host windows either, unless the host specifically implements such logic). There isn't even any obligation for a given host to handle keyboard events in ANY window, since it might just intercept them all from the message loop directly.

Post

mystran wrote: Thu Dec 06, 2018 6:57 pm Like I said, just about every modern toolkit can bubble events up the chain, but Win32 is not one of them. You can certainly implement such functionality in your application, but since it's not standard it's unreasonable to expect a random host to play along.
Ah! Thanks. Not what I wanted to hear, but it puts everything into perspective.

I've noticed that if I use the wrong-bittedness, my plugin works fine in the hosts that bridge them automatically. I'm assuming they are getting their own thread?

Short of doing this, what is the recommended method of getting the full plate of messages? Right now, I'm using a child EDIT window, but even this is not getting all necessary keystrokes.

You mentioned hooks before, which I know nothing about. Is there a resource you could point me to?

Thanks again for your help.
I started on Logic 5 with a PowerBook G4 550Mhz. I now have a MacBook Air M1 and it's ~165x faster! So, why is my music not proportionally better? :(

Post

Here is an idea. GetKeyState always returns the value you want regardless of whether the window has focus or not. You can further check if any part of your plugin has focus or not to decide if you really want to handle any user input at that time. To make that idea really work it's necessary to write a replacement for that edit control, too. After doing this, the host no longer has control over the user input of your plugin.
~stratum~

Post

Thanks! I'll look into it.

Give yourself +50 Karma points and consider my wrath appeased and this thread closed.
I started on Logic 5 with a PowerBook G4 550Mhz. I now have a MacBook Air M1 and it's ~165x faster! So, why is my music not proportionally better? :(

Post

syntonica wrote: Wed Dec 05, 2018 12:41 amAnother matter I just discovered is that if your host is handling multi-core support and your plugin is handling multi-core support, they don't really get along very well.
That's a big no-no. Currently plugins are assumed to be mono-thread. As long as we don't have a plugin API that passes a thread pool, don't multi-thread you're plugins. Be a good citizen.

Post

Miles1981 wrote: Sat Dec 08, 2018 10:57 am
syntonica wrote: Wed Dec 05, 2018 12:41 amAnother matter I just discovered is that if your host is handling multi-core support and your plugin is handling multi-core support, they don't really get along very well.
That's a big no-no. Currently plugins are assumed to be mono-thread. As long as we don't have a plugin API that passes a thread pool, don't multi-thread you're plugins. Be a good citizen.
Actually passing around a thread-pool solves only half the problem. Since you also want to put the "main" plugin processing on the same pool, you also need to abandon the simple blocking process() protocol (since blocking on pooled tasks from a pool worker thread is essentially a guaranteed dead-lock). Instead you need a system where a plugin can schedule it's processing into the pool once input is available and then notify (from whatever task finishes the processing) the host when output is done. While it's not that hard to design such an API, it might not be trivial to retrofit into existing APIs (or hosts for that matter).

Anyway... I don't think "big no-no" is entirely helpful approach either. Even with the "competition" between host and plugin threading, multi-threading can still be a huge win for plugins that otherwise would either take most of the available processing (wall-clock!) time (leaving nothing for dependent plugins down-stream) or simply couldn't finish in time at all (or you'd have to severely cripple polyphony, or whatever). The way I see it, it's a matter of choosing the lesser evil.

Post

mystran wrote: Sat Dec 08, 2018 1:47 pm Actually passing around a thread-pool solves only half the problem. Since you also want to put the "main" plugin processing on the same pool, you also need to abandon the simple blocking process() protocol (since blocking on pooled tasks from a pool worker thread is essentially a guaranteed dead-lock). Instead you need a system where a plugin can schedule it's processing into the pool once input is available and then notify (from whatever task finishes the processing) the host when output is done. While it's not that hard to design such an API, it might not be trivial to retrofit into existing APIs (or hosts for that matter).
Definitely. The plugin should only "publish" tasks to process with their dependency graph. Which is also why SOUL could be so interesting, because all this is abstracted and handled by the system itself.

Post

Miles1981 wrote: Sat Dec 08, 2018 5:17 pm Definitely. The plugin should only "publish" tasks to process with their dependency graph. Which is also why SOUL could be so interesting, because all this is abstracted and handled by the system itself.
I would personally not put any notion of "dependency graph" in such an API. It just adds unnecessary bloat, especially given that you might often want to make decisions on how to split the work at run-time.

If you instead just require that it's safe to queue more work from the workers themselves (ie. queue doesn't block on existing work items or full queue; it still safely fail as you can fall back to synchronous processing temporarily, so you can still use a real-time safe fixed-size queue for this), then any given plugin can implement whatever dependency logic they want and all you really need is for the host to tell plugin to process another block and the plugin to tell host that it's done.

That's basically the model I use for some stuff internally, because this way a single thread-pool can easily serve different types of tasks, from regular fixed dependency graphs (or graphs built in the GUI thread and swapped on the fly on block boundaries), to dynamic "split this task into N pieces" type of things, all without any dynamic allocation (or other potentially hazardous things) in any real-time thread.

Post Reply

Return to “DSP and Plugin Development”