Let’s say you have a MIDI generating plugin sending out note-ons and note-offs from processBlock().
Let’s say you have a “free-running” mode where the host (DAW) does not need to be playing, but your plugin can receive live midi input and generate arpeggios and riffs etc.
If the plugin is playing and generating MIDI that is being routed to other plugins to produce sound, and you delete it, of course some note-offs may not be generated for note-ons that have already gone out. And so you will get hanging notes.
I am of course storing the note-ons and note-offs so I can “stop” and generate note-offs for any currently sounding note-ons.
But how to do this before the plugin is deleted? There seems to be no way to know that you’re about to be deleted so you can take any steps like this. Is this just impossible to do anything about? If the user removes the plugin while it’s generating MIDI, it’s just his problem?
You can lock, and avoid being deleted until it’s a sent (or sent an all notes off message).
But that is not the way to go imho.
First of all, most (if not all hosts) have a panic button for this event (hanging notes).
Second of all, why generate notes when the host is not playing?
Some hosts will not even run your process block when the host is not playing.
Well, it’s an arpeggitor/phrase generator, so you can play live chords into it from an input (i.e. a keyboard) and hear what it will generate from those chords, and decide what kind of input you want to record onto a track by playing with it, without having to “play” the host track. It’s useful to be able to work with it before you want to commit to recording an input track.
As I’m still in development, I haven’t tried a lot of DAWs. I think it works with Reaper, Logic, Digital Performer, JUCE AudioPluginHost… Do you know of DAWs offhand where the processBlock is not sent unless the host is playing? And anyway, in that case, if it doesn’t work, it doesn’t work. But it’s still useful in the DAWs that support it.
You could override the processor’s destructor, but I think it’s better to override the processor’s reset method.
According to juce docs:
A plugin can override this to be told when it should reset any playing voices. The default implementation does nothing, but a host may call this to tell the plugin that it should stop any tails or sounds that have been left running.
At that point you could atomically signal to stop all playing notes and wait for that. Not a neat option, but the only way to do anything before deletion as far as I know.
That is a good reason.
Well, MIDI output only plugins tend to have some sketchy support in hosts. Just beware to mark it as a synth/generator and possibly setting tail length to a very long value. Been a while since I’ve dabbled with these type of plugins, but IIRC there where occasions where Pro Tools and/or Logic would suspend processing in some cases.
Yeah, Pro Tools will stop calling processBlock after a short period after stopping the transport (assuming you have ever started it), unless you set a really long tail time. I forget about Logic, (but I seem to recall ARA was different from non-ARA). And Cubase has a checkbox, which, if checked, will suspend processing if there is no audio.
Another idea here (if you find user’s are using synths that generally support it) is to send a MIDI “All Notes Off” command instead, since it might be easier to send it out than a bunch of note off messages if you want to minimize your contribution to how much the DAW hangs when it closes in this situation. You might also be able to get away with a shorter tail, for the same reason.
That sounds really strange… So if I want to record my guitar with a distortion plug-in, I will first have to record it, rewind, start play and first then I will hear how it will actually sound?! And I have to do this all over again every time I turn a knob to know the result?
Thanks for the ideas! This thing about a “tail” is new to me, as I don’t process audio (although my plugin is an “instrument”).
I’ve tried sending out note-offs or all note-offs in the Processor destructor, but they don’t go anywhere. This concept of a “tail” may be the missing piece - if it causes the plugin to hang around long enough to send out the note-offs.
I see there is a method AudioProcessor::getTailLengthSeconds(), but where do you specify or set a tail length?
EDIT: nevermind, I see you provide this by overriding getTailLengthSeconds, which I had set to “0” - Will experiment with this.
EDIT (again): I see i have no idea how to use this. I specified “5” as the return value for getTailLengthSeconds(), but this is never called by anything.
I tried implementing reset() which gets called prior to being deleted, but sending out anything here doesn’t seem to go anywhere and the plugin is still deleted instantly.
The destructor is the very last method to be called. Once it entered the destructor, no other method can be called without a crash (all members are invalid now). So when you try to send note offs, they still won’t be able to be delivered, because for that to happen processBlock would still need to run.
The host might call this to investigate the behaviour of your plugin. But since it is not processing audio, the host might not bother to call this.
I wonder, most hosts stop processing, when a plugin is added or removed. Only a few are designed to continue running in such a situation. most notably Ableton Live.
So may I ask on which host you get stuck notes, or is this a theoretical investigation?
Yes, again, the noteOff won’t be delivered once releaseResources() has been called. It would violate the api to call processBlock() after calling releaseResources().
Which reset() did you try?
Well, this is with JUCE AudioPluginHost. If I’m sending MIDI out to an instance of the Internal SineWaveSynth, and I delete my plugin, I get hung notes. Perhaps this is not a valid representation of the operation of most DAWs?
A plugin can override this to be told when it should reset any playing voices.
This is called by juce::VST3PluginInstance::deactivate(), prior to releaseResources() and the destructor. But it doesn’t seem to have any effect sending any note-offs from there.
I’m not really sure what you mean by “sending note offs from reset”, as the only way to output MIDI data to the host is from processBlock. If the host doesn’t call processBlock again, there is simply nothing your plugin can do.
Incidentally, I just tried this in Reaper (macOS), and it absolutely will let you delete the plugin while it is generating MIDI, resulting in hung notes. I had hoped to do something about this, but it seems not to be.
EDIT: anyway, Reaper has a “send all notes off to all midi outputs/plugins” action, and I’m sure most DAWs do as well, so I guess it’s the user’s problem.