JUCE native DLL invoked from Unity doesn't handle audio graph properly

I am working on a low latency DSP-like application in Unity (previous version here). After much experimenting I wound up settling on writing a JUCE-based native Windows library and a P/Invoke wrapper for it; all of this is open sourced under the name NowSoundLib.

I wrote a plain .NET app that uses this library to implement a little mini looper. The app works by setting up an AudioProcessorGraph to route the inputs to mix nodes (so I can do my own FFT – written before I found JUCE – on the input signal). When I start recording a new track, I create a new node for the new track and add connections from the appropriate input node to the new track node.

With JUCE using ASIO on a FocusRite 2i2, the latency is perfect and everything seems to be working great. I added some processBlock logging spam (once every 1,000 calls to processBlock), and here’s what I see when I start recording a new track:

NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 1, counterCount 71
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 2, counterCount 71
NOWSOUNDLIB LOG: NowSoundTrack::NowSoundTrack(1)
NOWSOUNDLIB LOG: NowSoundGraph::AddNodeToJuceGraph(6, 0)
NOWSOUNDLIB LOG: NowSoundTrack::processBlock: track 1, counterCount 1, state 1
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 1, counterCount 72
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 2, counterCount 72

So you can see that before I start recording, I’m getting processBlock calls to my input AudioProcessor nodes; and after I start recording (adding the connection to my new track node), I start also getting calls to processBlock on my new track AudioProcessor node – specifically, the “NowSoundTrack::processBlock” line. All of this is exactly correct.

BUT, when I run all of this under Unity, making exactly the same P/Invoke calls as are made by the .NET app, something goes wrong. The connection is evidently set up properly, but the track’s processBlock method never gets called:

NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 1, counterCount 8
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 2, counterCount 8
HandController.CreateLoopie(): player 0, hand Right, input AudioInput1
HandController.CreateLoopie(): player 0, hand Right, input AudioInput1, loopie Loopie(Clone)
LoopieController.Start(): started recording new track with input AudioInput1; track id 1
NOWSOUNDLIB LOG: NowSoundTrack::NowSoundTrack(1)
NOWSOUNDLIB LOG: NowSoundGraph::AddNodeToJuceGraph(6, 0)
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 1, counterCount 9
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 2, counterCount 9
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 1, counterCount 10
NOWSOUNDLIB LOG: NowSoundInput::processBlock: input 2, counterCount 10

Under Unity, it’s as though the new connection doesn’t exist, and input audio doesn’t flow to the new “track” AudioProcessor node.

The really frustrating part about this is that I’m using the Mono runtime, and with the current Visual Studio for Unity integration, native debugging isn’t supported. So I can breakpoint all through my C# code, but when under the debugger I can’t step from C# into C++. This is a big step backwards from debugging UWP Unity apps, which used the actual .NET runtime and hence let you debug across the managed/native boundary. (I had to write my own logging/polling P/Invoke API just to get log messages out of my DLL when running under Unity…) While I would ordinarily just debug down into JUCE itself, I can’t actually step into JUCE code while running under Unity.

So, some questions:

  • Anyone ever seen JUCE or the AudioProcessorGraph behave differently depending on the containing executable, and/or under Unity?
  • When using JUCE to implement a native Unity audio plugin, is there a way to actually debug that plugin when building a Unity PC (not UWP) app?

I know this is all kind of a vague question and a niche situation, but any suggestions would really be immensely helpful. I will probably wind up injecting logging upcalls into my fork of JUCE to try to debug this if there aren’t any other ideas I’m missing.

Thanks very much,
Rob