Calling setValueNotifyingHost() from processBlock()


#1

Is it safe to call setValueNotifyingHost() (and beginChangeGesture()/endChangeGesture()) from within the processBlock()?

I’ve been doing this with no issues for some time. Works perfectly in Pro Tools, for example. But, I have a Logic user reporting issues with distortion and noise.

Am I doing something ill advised, or is this a Logic idiosyncrasy?


AudioThreadGuard - keep your audio thread clean
Class for detecting unsafe operations in realtime contexts
#2

These are supposed to be called from the GUI thread, when a user is e.g. dragging the mouse to adjust a value via a slider, knob or other continuous changes.

Why are you making these calls from the audio-thread?


#3

I call from the process thread because this is where the values are produced. In other words, there is no physical slider or other control associated with this value.

As mentioned, this works in all DAWs I’ve tried, on both Mac and PC, except in Logic.

In fact, it works quite well in Pro Tools, for example.

So, I am wondering if I am just “lucky” up until now, or if this is one of Logic’s quirks?


#4

So you are intending to send parameter automation information that the host can than record and play back from the audio-thread? Can you please elaborate what you’re doing? I’m quite curious.


#5

I don’t want to get too specific on exactly how I am using this, but it provides automation data to and from the host for controlling how the audio is processed. So, it is an internal value used by the processBlock() that is automatable! The whole point is that it is calculated automatically, and therefore there is no physical control associated with it.

My code inside the processBlock() is very tight. So, maybe it has not been an issue on other DAWs because of the way they handle the processing and automation. But, I know Logic does certain things differently, and so I ask the question as to whether this is just unique to Logic?


#6

I wouldn’t know if this is specific to Logic, but I have a suggestion:

If the value is calculated and used internally, then why do you need to tell the host and let these internal calculations be recorded etc.? Why not keep it all internal? If you have one control-value that can be automated by the user, and your algorithm is deterministic, then the same internal values would be generated, so there is really no need for the host to even know about these internal values.

If my understanding is correct, you can simply remove a chunk of code and not worry about it anymore. It would also fix the problem. The fastest code is the one that doesn’t need to be executed.


#7

You are misunderstanding how it is used. The value is usually not exposed in other plugins. It is a unique feature of my plugins that this value is exposed and automatable! So, the purpose here is to give the user access to this value, when that is normally not an option. In other words, the user can record the automation of this value, and then choose to edit the automation to tailor the value to best suit the track, or simply view what the plugin is doing.

This is not an experiment. It works. And, I have been shipping plugins with this feature for almost a year.

My post was prompted because a Logic user reported artifacts when using the automation recording/playback feature. Since it has not been reported by any other users, and I know from my own testing that it works as expected in other DAWs, I am simply enquiring as to whether this is just a Logic thing?


#8

OK. Strange design decision, but if it works, it works.


#9

Nope, that definitely isn’t safe, I’m afraid.

Just because you haven’t heard of any problems doesn’t mean it’s safe. You’ve no idea what the host will do when you invoke that call, but it’s very likely that some of them will allocate memory or post a message, which will occasionally cause problems depending on what else the host is doing at a particular moment.

The only safe way to send events from your process method is to have your own lock-free fifo to post events which you later pick up in another thread (or on a timer) and do the work there.


#10

I don’t know about any particular quirks in Logic, but you need to make sure that anything you do in AudioProcessor::setValue() or AudioProcessorListener::audioProcessorXxxxx is thread-safe, fast and non-blocking as these are called as a result of the methods you mention.

It may just be that Logic is less forgiving of something going on in there…


#11

Yes, thank you. I was afraid that I have just been “lucky” until now. I always strive to make things better, however, so this is a great opportunity to refactor the code into something more robust!

The lock-free fifo idea sounds like it may very well be the way to go. I’ll be looking at that as my new approach.

In the mean time, I’ve been analyzing my code, out of curiosity, to try to determine why it works as well as it does? I mean, as I’ve mentioned three or four times, it works great in Pro Tools. You’d think it would fail there, if it was going to fail anywhere. And, it also works great in Tacktion/Waveform (you may have some insight here).

I make a point of making all my code in the processBlock() as tight as possible. And, for example, I bypass code that is not changing, which includes the setValueNotifyingHost() calls. In fact, in a steady signal, dozens, if not hundreds, of blocks can go by without setValueNotifyingHost() being called. So maybe this has something to do with my “getting away with it”.

So, I’ve got work to do. And, the lock-free fifo idea is not too different from the way I feed data to my meters (although, they are on a much more relaxed schedule!). We are always learning. I think that is one of the reasons we code!

Thank you for your great help!


#12

My approach is the problem for sure. But others may want to take note that Logic is doing something different, because my plugins are OK in most DAWs, and, yet, they have definite issues in Logic.

So, I’ll be changing what I am doing. And, Jules lock-free fifo sounds like a good approach (maybe with a timer).

Thank you so much for your help!


#13

Maybe it is even only a problem in a combination, like having a certain amount of plugins, you can’t know… still a good idea to improve it.
It doesn’t sound, like it’s ever going to be sample accurate, so why not simply use callAsync with the setValueNotifyingHost()? Sounds easier to implement…


#14

Then this would make a good addition to the documentation of those methods?

I remember that I recently had to suggest the same also for the thread-safe methods for obtaining/loading a ValueTree.

What do you think about tagging in some standard way the methods that are not realtime-safe (or, conversely, those that are safe for being called from the realtime thread)?


#15

Great suggestion! Maybe as an addition to all methods a hint of “Audio Thread - Message Thread - Any Thread”? That woulkd be very helpful


#16

Yes, definitely should be added to the docs.

@t0m maybe while you’re working on the audioprocessor stuff, we should throw in some assertions to make sure people don’t call unsafe stuff from the audio thread? We could have a thread-local flag set during the process method, and have assertions that check for it, kind of like we do in some places for the message thread.


#17

I don’t think this will work reliably, because there are hosts that call initialization methods from realtime threads before calling any process callback with them.

And therefore, all the methods called in those initialization phases will have a wrong indication because said hypotetical isRealtimeThread() method will return false at that point, but it will start returning true later on after the first processing callback has been called.

To be fair, on the other hand this may be a tolerated behavior, if we accept that “blocking” behavior can happen on a soon-to-be realtime thread, as long as it has not yet actually entered the processing loop


#18

Yes, I think that’s pretty much what you want. If a host calls initialisation routines, it must expect them to block, so it doesn’t matter what thread it uses for them.


#19

Uhm ok.

Playing the “devil’s advocate” now: what happens if the DAW decides to stop using a certain thread of its pool for realtime jobs, and assigns it to background stuff that still call into the plug-in?
That thread would still be marked as realtime while not really being anymore.

Maybe while designing this, allow for another method to query whether the current DAW is to be trusted or not. I suppose most DAWs will be implemented in a sensible way and the answer will be “yes”, but you may never know.


#20

I think you misunderstood what I meant: I was suggesting having a thread-local variable “isAudioThread”, which is set to true at the start of process() and set to false at the end. That way it doesn’t matter if the thread changes.