Getting started with tracktion in a plugin 2022

I am trying to build a piano roll and synth that will be hosted in a DAW as a plugin. I was planning on using tracktion to accomplish this, but I’m pretty new to it.

I have been having a hard time figuring out how to integrate the engine with juce::AudioProcessor. I tried poking around EngineInPluginDemo.h, but I can’t get the demo running so I’m not sure how useful it is. (In case someone knows how to fix it, the demo seems like its hanging indefinitely while calling juce::AudioDeviceManager::getAvailableDeviceTypes).

It seems like most of the questions on this topic were asked prior to official support being added, so I’m hoping someone can provide guidance on how to hook things up. Specifically what should the AudioProcessor::prepareToPlay and AudioProcessor::processBlock look like.

Can you try using the develop branch as that should work using the CMake project.

If getAvailableDeviceTypes is still hanging, can you post a stack trace. It’s probably hanging in something deeper or deadlocked somewhere.

tldr: The engine appears to have a problem scanning Loopback

I am having the same problem on the develop branch.

The program never actually crashes, so the most I get for output to the terminal is this

Loopback Audio
Creating Default Controllers...
Edit loaded in: 2 ms

After spending some time digging with breakpoints I was able to get a little deeper. It hangs while trying to construct a AudioObjectGetPropertyData on line 2098 of juce_mac_CoreAudio.cpp. Here is the stack trace up to that point:

Thread-1-<>-[Juce Message Thread]
juce::CoreAudioClasses::CoreAudioIODeviceType::getNumChannels(unsigned int, bool) juce_mac_CoreAudio.cpp:2258
juce::CoreAudioClasses::CoreAudioIODeviceType::scanForDevices() juce_mac_CoreAudio.cpp:2101
juce::AudioDeviceManager::createDeviceTypesIfNeeded() juce_AudioDeviceManager.cpp:138
juce::AudioDeviceManager::scanDevicesIfNeeded() juce_AudioDeviceManager.cpp:533
juce::AudioDeviceManager::getAvailableDeviceTypes() juce_AudioDeviceManager.cpp:166
tracktion::engine::HostedAudioDeviceInterface::initialise(const tracktion::engine::HostedAudioDeviceInterface::Parameters &) tracktion_HostedAudioDevice.cpp:314
EngineInPluginDemo::EngineWrapper::EngineWrapper() EngineInPluginDemo.h:197
EngineInPluginDemo::EngineWrapper::EngineWrapper() EngineInPluginDemo.h:195
std::make_unique<…>() unique_ptr.h:728
<lambda>::operator()() const EngineInPluginDemo.h:234
EngineInPluginDemo::callFunctionOnMessageThread<…>(<lambda> &&) EngineInPluginDemo.h:216
EngineInPluginDemo::ensureEngineCreatedOnMessageThread() EngineInPluginDemo.h:234
EngineInPluginDemo::prepareToPlay(double, int) EngineInPluginDemo.h:54
juce::AudioProcessorPlayer::setProcessor(juce::AudioProcessor *) juce_AudioProcessorPlayer.cpp:193
juce::StandalonePluginHolder::startPlaying() juce_StandaloneFilterWindow.h:248
juce::StandalonePluginHolder::init(bool, const juce::String &) juce_StandaloneFilterWindow.h:110
juce::StandalonePluginHolder::StandalonePluginHolder(juce::PropertySet *, bool, const juce::String &, const juce::AudioDeviceManager::AudioDeviceSetup *, const juce::Array<…> &, bool) juce_StandaloneFilterWindow.h:103
juce::StandalonePluginHolder::StandalonePluginHolder(juce::PropertySet *, bool, const juce::String &, const juce::AudioDeviceManager::AudioDeviceSetup *, const juce::Array<…> &, bool) juce_StandaloneFilterWindow.h:84
juce::StandaloneFilterWindow::StandaloneFilterWindow(const juce::String &, juce::Colour, juce::PropertySet *, bool, const juce::String &, const juce::AudioDeviceManager::AudioDeviceSetup *, const juce::Array<…> &, bool) juce_StandaloneFilterWindow.h:731
juce::StandaloneFilterWindow::StandaloneFilterWindow(const juce::String &, juce::Colour, juce::PropertySet *, bool, const juce::String &, const juce::AudioDeviceManager::AudioDeviceSetup *, const juce::Array<…> &, bool) juce_StandaloneFilterWindow.h:718
juce::StandaloneFilterApp::createWindow() juce_StandaloneFilterApp.cpp:80
juce::StandaloneFilterApp::initialise(const juce::String &) juce_StandaloneFilterApp.cpp:98
juce::JUCEApplicationBase::initialiseApp() juce_ApplicationBase.cpp:297
juce::JUCEApplication::initialiseApp() juce_Application.cpp:92
juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:256
juce::JUCEApplicationBase::main(int, const char **) juce_ApplicationBase.cpp:240
main juce_audio_plugin_client_Standalone.cpp:47
start 0x00000001223b151e

I noticed a couple things during my investigation.

  1. The process of scanning for devices is successfully run twice before failing during construction of the EngineWrapper.
  2. I have 7 devices being scanned, and the only one that causes the hang is the last device, which is an instance of Loopback that I have running.

When I turn off Loopback, the plugin demo is able to run. I am otherwise able to run JUCE plugins and Tracktion Engine applications with Loopback turned on, its only when I try to hook the engine up to a plugin that it trips up on Loopback.

EngineInPluginDemo is now able to

  • Build
  • Run standalone
  • Be loaded as a plugin
  • Pass-through incoming audio signals

However, there are a few things I was expecting it to do which it cannot.

Firstly, the plugin’s transport does not appear to be synching with the host.

The Host Info section is not displaying the correct tempo, and does not update when the host begins playing.

Secondly, while taking a look at the code, I noticed that it looks like it is trying to allow for MIDI I/O, and even has a FourOscPlugin with an organPatch setup. So I assume that it is meant to be able to accept MIDI to play an organ sound, but no dice. I tried loading the demo in AudioPluginHost and its definitely not accepting MIDI.

Perhaps if someone can help me get these two things working, the demo will make for a suitable guide.

So the reason that MIDI isn’t being properly handled has something to do with juce_add_pip failing to recognize the JucePlugin_WantsMidiInput=1 and JucePlugin_ProducesMidiOutput=1 flags in EngineInPluginDemo.h.

After some code surgery I managed to get the bulk of EngineInPluginDemo.h into my own project and now the plugin is properly handling I/O of both MIDI and audio. Well, almost.

The current implementation from EngineInPluginDemo.h frequently misses MIDI events. It is really easy for me to hit a note on my keyboard and it will just hang until I press it again because HostedAudioDeviceInterface::processBlock() missed the note off event.

I am beginning to wonder if Tracktion Engine simply can’t handle this use case.

I am facing a similar issue not with Loopback but with my UA audio interface.

Launching the EngineInPlugin as a standalone will hang forever. If I turn OFF my audio interface the standalone loads correctly.

It seems adding a call to prepareToPlay from the StandalonePluginHolder::createPlugin() will make the standalone load properly (even with the audio interface turned ON). Not too sure why at this stage but it might be due to the tracktion engine not being created until prepareToPlay() is called.

Ok, I’ve had chance to take a look at this now, I can’t however replicate the hang issue so it’s quite hard for me to investigate properly. It sounds like something a bit ropey in the UA or Loopback drivers, possibly that they don’t like being queried too many times.

I think however this can be worked around. When running as a plugin, Tracktion Engine doesn’t actually need access to all your devices, it creates a fake one and injects that in to the juce::AudioDeviceManager (that’s the HostedAudioDeviceType). The problem seems to be that calling getAvailableDeviceTypes to return the one we’ve already added causes a hang.

We could maybe create a subclass of AudioDeviceManager and override AudioDeviceManager::createAudioDeviceTypes to avoid creating system devices when not needed. This would probably need a new method in EngineBehaviour to control as there probably will be cases where people want to also access the system devices. I’ll take a look at that on a branch if you’re willing to test it?

The host info should definitely be synced, you can see in this image how it follows the tempo change in Waveform:

I think that fact that JucePlugin_WantsMidiInput=1 and JucePlugin_ProducesMidiOutput=1 is a juce bug. Perhaps @reuk could take a look? If I add those as target_compile_definitions directly to my CMake I get re-definition warnings/errors so I’m not quite sure the “correct” place to specify them.

The correct way to enable MIDI in/out in a PIP is to add pluginWantsMidiIn and pluginProducesMidiOut to the pluginCharacteristics field in the PIP metadata block - or at least, that’s what the AudioPluginDemo does, and MIDI in/out works there.

Thanks for this @reuk. Is there any documentation to the spec of the PIP fields anywhere? I’ve had to dig through the Projucer source before but now that logic is probably spread around cmake files and juceaide?

Having a spec here next to the other docs would be very useful.

It’s not particularly intuitive e.g. pluginProducesMidiOut maps to JucePlugin_ProducesMidiOutput=1 which doesn’t have the same separation, capitalisation or abbreviation.

@jdh9862 @lowertonic can you check out this branch and test it again please? GitHub - Tracktion/tracktion_engine at bugfix/engine_in_plugin

If you’re building your own plugin, not the demo, please ensure you implement the new EngineBehaviour method to return false:
bool addSystemAudioIODeviceTypes()

Let me know if this helps.
I’ll take a look at missing MIDI input soon.

Hi @dave96,

this seems to work for me. Thanks for looking into it.

Thanks, that’s now on develop.

1 Like

The new change has solved my hanging problem :grin:

Unfortunately the demo still isn’t processing MIDI for me. By running the demo in the plugin host, I am able to see that IO connections are working as expected.

Using a sine wave synth, I am able to demonstrate that audio can pass through the demo just fine :+1:

However, while the demo can now accept MIDI, it does not generate sound from the MIDI, nor does it allow the MIDI data to pass through (as shown by the pair of MIDI loggers). So it seems like the demo is discarding the MIDI.