surge-synthesizer / surge

Synthesizer plug-in (previously released as Vember Audio Surge)
https://surge-synthesizer.github.io/
GNU General Public License v3.0
3.11k stars 397 forks source link

Running surge in pd/sc plugin layer and bus configuration #5877

Closed Spacechild1 closed 2 years ago

Spacechild1 commented 2 years ago

Hi, I have developed a VST plugin host for Pure Data and SuperCollider (https://git.iem.at/pd/vstplugin/).

I can successfully play the Surge VSTi in Pure Data, but in SuperCollider the "Output" meter says "Audio Output Unavailable" and the output stays silent. What does this message mean and under which circumstances does it appear?

There is probably a subtle bug in my SuperCollider implementation and maybe you can point me in the right direction.

Thanks a lot in advance!

baconpaul commented 2 years ago

that sounds like a cool project.

Anyway That happens if process is never called or if process is called with an invalid bus configuration. Specifically if you can process and don’t have at least one stereo output main bus the Audio will be disabled. If you build a debug version of scxt you can see in src surge-xt surge synth processor where we set audio active to true and you will either not call process or punch out in one of the conditions.

Let me know if you need help.

baconpaul commented 2 years ago

Specifically a breakpoint here and ahead of it

https://github.com/surge-synthesizer/surge/blob/c6449b70487ab3f3c4443c6c34e2d393f7067e93/src/surge-xt/SurgeSynthProcessor.cpp#L244

That toggle to true sets the processing available flag which controls both the sound coming out and the ui display

Spacechild1 commented 2 years ago

Thanks for your reply!

I've just found the actual issue: When I call IAudioProcessor::setBusArrangements with an empty input bus (SpeakerArr::kEmpty), the plugin happily accepts this arrangement (verified by calling IAudioProcessor::getBusArrangements), but afterwards it displays the "Audio Output Unavailable" message and refuses to play. This is even more confusing because it really is about the input bus.

Note that when I pass a stereo input bus instead of an empty bus, it works as expected.

I never had this problem with any other VSTi... Maybe it's a JUCE problem. The other JUCE based VST3 plugins I have, like Dexed or Helm, don't have an input bus, so I can't really compare...

Here's the relevant code: https://git.iem.at/pd/vstplugin/-/blob/master/vst/VST3Plugin.cpp#L1341

Spacechild1 commented 2 years ago

(Tell me if I should move this to a new proper bug report.)

baconpaul commented 2 years ago

Surge has a very weird topology that it is an instrument (no main input) with a side chain (aux main input). I’ll take a peek at your code and my code but the confusion is there somewhere I’m sure. I might be mis-rejecting a main input

baconpaul commented 2 years ago

Hmm looking at my code I should reject that topology. I am assuming it isn’t super easy to run this yeah? Oh and we can keep using this issue I’ll just change the title.

Can you do fx as well as instruments? I’m curious if the surge fx bank works since it also has a sidechain

Spacechild1 commented 2 years ago

Thanks for the quick reply!

Hmm looking at my code I should reject that topology

Since the input bus is an aux bus, it would make sense to accept an empty bus. The plugin should still work without the side chain input.

I am assuming it isn’t super easy to run this yeah?

What do you mean by that?

I’m curious if the surge fx bank works since it also has a sidechain

Surge generally doesn't produce any output when passing an empty input bus.


When trying to pass an empty input bus + a stereo output bus (+ 2 empty output busses) to Surge XT I get the following result (this is the verbose output of my plugin host):

requested bus arrangement:
input bus 0: 0ch
output bus 0: 2ch
output bus 1: 0ch
output bus 2: 0ch
actual bus arrangement:
input bus 0: 0ch
output bus 0: 2ch
output bus 1: 2ch
output bus 2: 2ch

You can see that it accepts the empty (aux) input bus, but rejects the empty (main) output busses. This actually looks fine. The only surprising part is that Surge stays silent and says "Audio Output Unavailable" :-)


For comparison, here's the output of Surge XT Effects:

requested bus arrangement:
input bus 0: 0ch
input bus 1: 0ch
output bus 0: 2ch
actual bus arrangement:
input bus 0: 2ch
input bus 1: 0ch
output bus 0: 2ch

The empty main input bus is rejected, the empty aux input is accepted. This looks fine as well.

Spacechild1 commented 2 years ago

I just tried to pass a stereo main input bus + an empty aux input bus to Surge XT Effects and it shows a similar problem: the plugin doesn't process the input, but rather passes it through unchanged. Only when I provide a non-empty aux input bus, the effects actually work.

Generally, I think it's fine that both plugins accept empty aux inputs (that's expected behavior), but they should also produce sound :-)

baconpaul commented 2 years ago

What I meant with the “super easy to run” is “how do I run my plugin in your host”. I’m on mac with at best a casual understanding of sc but have built lots of software

i agree the plugins should work in these configurations. To figure out why they don’t one of us needs to be in the process loop of our plugin with your host stopped on a debugger breakpoint. If it’s me I need to know how to run your stuff. If it’s you we happily take prs. :)

Spacechild1 commented 2 years ago

What I meant with the “super easy to run” is “how do I run my plugin in your host”. I’m on mac with at best a casual understanding of sc but have built lots of software

I see! Here's how you do it:

  1. Install the latest SuperCollider version: https://supercollider.github.io/download
  2. Download the latest VSTPlugin release: https://git.iem.at/pd/vstplugin/-/releases
  3. Extract the .zip file in your SuperCollider extension directory. Windows: C:/Users/<you>/AppData/Local/SuperCollider/Extensions macOS: ~/Library/Application Support/SuperCollider/Extensions Linux: ~/.local/share/SuperCollider/Extensions
  4. Start the SC IDE and boot the Server (Cmd+B). You should see a message VSTPlugin 0.5.4 in the console.
  5. Attach to the scsynth.exe process in your debugger.
  6. Execute the following code in the SC IDE. Single lines are executed with Shift+Enter; code blocks in parantheses are executed with Cmd+Enter.
    
    // 1. create VSTi synthdef (no input, stereo output)
    (
    SynthDef.new(\vsti, { arg out = 0;
    Out.ar(out, VSTPlugin.ar(nil, 2));
    }).add;
    )

// 2. create Synth ~vsti = VSTPluginController(Synth(\vsti));

// 3. open Surge debug version ~vsti.open("/Surge XT.vst3")

// 4. open the editor ~vsti.editor;



Let me know if you have problems!
baconpaul commented 2 years ago

Awesome. I will look this week.

baconpaul commented 2 years ago

Oh and excellent directions. I followed them, xattrd the scx, and am good. Now I just to set up my CLion to do debug x86 builds on this m1 and I should in theory be able to stop in the debugger!

baconpaul commented 2 years ago

So when i run it, i never even get into surge processBlock (like - i put a print in there and it never gets called). That explains why the message is on.

something well north of that is not happening.

Let me see if I can reject the active but empty input structure.

Spacechild1 commented 2 years ago

I found the problem!

When the user requests 0 channels for an aux bus, I don't verify the channel count but instead just deactivate the bus. I quickly changed the code to always verify the channel count and now I see that Surge really wants stereo input for the aux input bus.

Because I deactivated the aux input bus, I pass an empty buffer in the processing function, as advertized by the VST3 docs. Surge, however, expects the bus to be stereo, that's probably why it bails.

So I think the problem really is that Surge ignores the fact that I have deactivated the bus. Can you verify this?


Apart from that, what I could do is to only deactivate an aux bus if the plugin accepts the 0 channel count.

baconpaul commented 2 years ago

OK I stepped through it in the code and found out where JUCE is kicking you out

juce wraps the process in this

if (totalInputChans == pluginInstance->getTotalNumInputChannels()
                 && totalOutputChans == pluginInstance->getTotalNumOutputChannels())               

and you fail that test because your totalInputChans == 0

I think this might be because you are conflating bus configuration with input shape. But this always confuses me too. It looks like surge (via juce) is expecting a mono or stereo or disabled input bus - we can cope with any of those and so we advertise that. But the plugin requires two channel topology even if you don't use them. Basically "Bus layout" and "pointers" are different.

You can see this if you look inside the juce VST3 host in fact. It has code which looks like this:

 for (int i = getTotalNumInputChannels(); i < buffer.getNumChannels(); ++i)
            buffer.clear (i, 0, numSamples);

that is, it clears the input buffers it hasn't mapped.

So your 'empty' statement is a correct indication that logically we can handle you not populating the inputs. But that layout is different than the bus which is 2-in-6-out-no-matter-what-you -use

If you don't want 6 out you can only listen to 1 or 2 out. But you have to pass us the 6. Similarly if you don't want 2 in you can disable or pass us 1 or 2 in but we need the 2 pointers.

Or put another way: what we tell you with getBusCount is kinda what you have to do. How you wire that is up to you. But the slots have to be there. That seems to be how it is working in the JUCE code and the VST3 api.

I haven't dug much deeper but the 0-vs-2 is clearly why we are stopping and also clearly not what the JUCE VST3 host (or reaper, logic, cubase, bespoke, live etc...) do. It is invariably more subtle than this too.

baconpaul commented 2 years ago

I found the problem!

When the user requests 0 channels for an aux bus, I don't verify the channel count but instead just deactivate the bus. I quickly changed the code to always verify the channel count and now I see that Surge really wants stereo input for the aux input bus.

Because I deactivated the aux input bus, I pass an empty buffer in the processing function, as advertized by the VST3 docs. Surge, however, expects the bus to be stereo, that's probably why it bails.

So I think the problem really is that Surge ignores the fact that I have deactivated the bus. Can you verify this?

Apart from that, what I could do is to only deactivate an aux bus if the plugin accepts the 0 channel count.

ha we drew the same conclusion

Surge is absolutely fine with a deactivated bus, a mono bus, or a stereo bus, but transports that on a stereo buffer.

So I need 2 channels of which you can use 0, 1, or 2.

Spacechild1 commented 2 years ago

Ok, looks like I've slightly misread the VST3 docs:

The size of the channel buffer array must always match the number of channels. So the host must always supply an array for the channel buffers, regardless if the bus is active or not. However, if an audio bus is currently inactive, the actual sample buffer addresses are safe to be null.

https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1AudioBusBuffers.html

So yeah, I really have to always check the channel count. I can't just deactivate the bus for 0 channels because the plugin might not actually accept that arrangement.

On the other hand, I think Surge really should accept 0 channels for all aux busses.

baconpaul commented 2 years ago

it does. it handles those nulls with no problem.

I also notice you only call setBusArrangements once and you all it with an input. Perhaps after you re-confiugre you need to call that again? I am almost 100% sure that if you do call setBusArrangements with 0 inputs, surge will work, but I never see that call. So as a result, I expect the inputs. And then I don't get them.

Spacechild1 commented 2 years ago

What happens is that I request 0 input channels, but Surge rejects that arrangement and tells me it needs 2 channels. This is the output of my modified host:

requested bus arrangement:
input bus 0: 0ch
output bus 0: 2ch
output bus 1: 0ch
output bus 2: 0ch
actual bus arrangement:
input bus 0: 2ch
output bus 0: 2ch
output bus 1: 2ch
output bus 2: 2ch

What I should do is deactivate the aux input bus and provide 2 channels nevertheless (the actual channel buffers can be null). But I'm wondering why Surge rejects the 0 channels for the aux input bus?

baconpaul commented 2 years ago

oh right well that's a separate issue. I would have to dig more into juce code and my bus answers to figure that out.

But it seems like surge is giving valid answers and you can fix your bridge right? That is, it is telling you "no" and if you listen to the "no" then everything works? The fact that maybe it should say "yes" is separate?

Spacechild1 commented 2 years ago

But it seems like surge is giving valid answers and you can fix your bridge right? That is, it is telling you "no" and if you listen to the "no" then everything works?

Exactly!

The fact that maybe it should say "yes" is separate?

I agree.

Thanks a lot for your help!

baconpaul commented 2 years ago

if that's correct then perhaps the thing to do is make it so you still ask, and then make it so i can run the code that asks, and then we can step through that once you have it working with the respect of the no answer and appropriate physical topology based on that?

(and this stuff is really hard; i helped figure it out for bespoke and am still working on it for CLAP. Total pain in the neck).

baconpaul commented 2 years ago

Cool well lets keep this issue open but I will knock it ouf of our milestone. If you have a release I can run in SC where you try and ask for 0 I can trap that in debugger and see why we don't say yes (or if we could).

Glad we got it working!

Spacechild1 commented 2 years ago

If you have a release I can run in SC where you try and ask for 0 I can trap that in debugger and see why we don't say yes (or if we could).

Actually, the example code above should do exactly that!

baconpaul commented 2 years ago

It asks yes, and I respond that any disabled bus is ok. I out prints in there even. And so you call set bus arrangements with 1 1 then probe the busses and I say disabled to stereo is ok and then you don’t call setbusarragements again!

if you build surge yourself you can see the calls

baconpaul commented 2 years ago

But anyway why not merge your fix and let’s make it work then I can build that and probe - sound good?

Spacechild1 commented 2 years ago

But anyway why not merge your fix and let’s make it work then I can build that and probe - sound good?

Ok!

Spacechild1 commented 2 years ago

Here's a branch with the fix: https://git.iem.at/pd/vstplugin/-/tree/vst3-bus-fix

Here are binaries: https://git.iem.at/pd/vstplugin/-/jobs/35892/artifacts/download?file_type=archive

Generally, I'm happy now. The original issue is resolved and it was really a bug in my host implementation.


The only "issue" remaining is that Surge XT doesn't accept an empty aux input bus with setBusArrangements and insists on stereo input. However, I just tried a few plugins that have side-chain inputs and they show a similar behavior. Don't bother!

I'll just go ahead and close this issue. Thanks a lot for your help!

Spacechild1 commented 2 years ago

BTW, Surge is awesome! Controlling it algorithmically from SC or Pd is big fun!

baconpaul commented 2 years ago

Awesome! And yes I was rather excited for it to work. I’ll try and make some music with it :) and glad you enjoy surge!