Tracktion in QT app - close, but quiet :)

Hi!

I put together a quick test to see if I could get Tracktion to play sound inside a QT app.

AudioDeviceManager::audioDeviceIOCallbackInt gets called regularly so that’s not it.

Quite quickly after starting, the transport stops itself from an internal timer callback.

Even if I step through the transport start to make sure it’s called, on exit isPlaying is always false.

The code is a minimal QT hello world, and the simple Playback Tracktion demo bashed together.

Any tips?

#include
#include

#include <tracktion_engine/tracktion_engine.h>

#include “src/PlaybackDemoAudio.h”
#include “src/Utilities.h”

namespace te = tracktion;
using namespace std::literals;
using namespace te::literals;

int main(int argc, char* argv)
{
juce::initialiseJuce_GUI();

// Create the engine
te::Engine engine{"Tracktion Hello World"};

std::unique_ptr<te::Edit> edit;

const auto editFilePath = juce::JUCEApplication::getCommandLineParameters().replace ("-NSDocumentRevisionsDebugMode YES", "").unquoted().trim();

const juce::File editFile (editFilePath);

auto f = juce::File::createTempFile (".ogg");
f.replaceWithData (PlaybackDemoAudio::BITs_Export_2_ogg, PlaybackDemoAudio::BITs_Export_2_oggSize);

edit = te::createEmptyEdit (engine, editFile);

// edit->getTransport().addChangeListener (this);

auto clip = EngineHelpers::loadAudioFileAsClip (*edit, f);
EngineHelpers::loopAroundClip (*clip);

auto& transport = edit->getTransport();

// Neither works
// EngineHelpers::togglePlay (*edit);

// transport.play(false);

QApplication a(argc, argv);
QPushButton button("Hello world!", nullptr);
button.resize(200, 100);
button.show();

int toReturn = QApplication::exec();

// And this is never true on exit
if (transport.isPlaying())
{
    std::cout << "Was playing" << std::endl;
    transport.stop(true, true, true);
}

juce::shutdownJuce_GUI();

return toReturn;

}

I think you’re going to run into issues because both Qt and JUCE have their own message loop.

There are some forum posts about getting Qt and JUCE working together. It does seem to be possible but I don’t think it will be easy.

Good luck!

Yeah, I don’t know how the two message looks will interplay.

Can you put a breakpoint and share a stack trace of the timer callback that’s causing playback to stop though? That might shed some light.

1 Like

Thanks @adamwilson, I had googled a bit indeed, and found some older posts about it which don’t quite translate / build with more recent versions - (I asked about it here too with no replies).

I’ve not spent much time on it yet, but yes I can imagine possible worst-case scenarios where even with the dual message loop it should be at least possible - for example the two loops talking over queues with each other, each in its own thread, or process (surely that’s overkill).

Thank you for checking in @dave96!
I know QT for plugin UI’s is not recommended but for a standalone application/host it still makes sense, so it should be worth trying to get a POC running.

I’m afraid the stack trace isn’t particularly exciting:

tracktion::engine::TransportControl::stop(bool, bool, bool) tracktion_TransportControl.cpp:904
tracktion::engine::TransportControl::timerCallback() tracktion_TransportControl.cpp:1075
juce::timer::TimerThread::callTimers() juce_Timer.cpp:170
juce::timer::TimerThread::CallTimersMessage::messageCallback() juce_Timer.cpp:271
juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93
juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104
juce::MessageQueue::runLoopSourceCallback(void *) juce_MessageQueue_mac.h:112
main main.cpp:48

I’ll carry on experimenting.

Actually I got this one to build and run now:

After altering the CMakeLists.txt a bit it pulls in Tracktion.

CMakeLists.txt (2.0 KB)

LV2 example doesn’t build but I’m sure that’s because the installation is missing on my mac or something.

The JUCExQT example runs and the JUCE audio device control panel’s test sound plays. That’s as far as I got.

Now I’ll pull in some Tracktion example code into the above and see if I get that to work!

Ok, that’s actually useful because it’s saying the playHeadWrapper (which is the internal playhead) isn’t playing.

I have a feeling it’s because the juce message loop isn’t running so the audio callbacks and async updaters aren’t being triggered.

I think that would be the next place to look.

1 Like

Thanks @dave96!

I just got the opposite route working for the time being: using the above “official” JUCExQT example, into which I put TracktionEngine.

In JUCExQT JUCE’s message loop does run, and the JUCE window serves up an animated QT window inside, which doesn’t seem to be a problem.

The EngineInPluginDemo code from the Tracktion repo worked swell now that I moved it over into JUCExQT to test, and if I run the Standalone build target I don’t suppose I’m missing out on important functionality from Tracktion, since this is meant to be a standalone application.

On to learning more about Tracktion now!

1 Like

False alarm:

While I got the “organ” from the EngineInPluginDemo transplanted and working into the JUCExQT example, and replaced JUCE with tracktion there, I still see the same “stopping” behaviour when I try to start playing back the audio clip copied over from the Playback demo.

I should find a way to check that the message loop is running - but @dave96, given that the EngineInPluginDemo “organ transplant” responds correctly to MIDI input, and sound plays back, are you sure the reason has to be that the message loop isn’t running?

Thanks!

I made a quick test - I triggerAsyncUpdate() in processBlock, and print to the console in handleAsyncUpdate. I get a stream of printouts, so the JUCE MessageManager must be running I figure!

Edit. The plot thickens. I removed all QT code from CMakeLists.txt and the plugin GUI and still get the same problem.

Actually no, the message loop must be running if the Timer callback is happening.

1 Like

I should go back to a very simple non-qt project where I get the Playback demo play the sample inside of the EnginePluginDemo - since I just commented out all QT and still get this issue, it’s not related to anything to do with that, dual event loops, or anything else of the kind.

Can you step through that playHeadWrapper->isPlaying() call in the timer callback to see why it’s returning false?

I.e. TransportControl::PlayHeadWrapper::isPlaying():

    bool isPlaying() const
    {
        if (transport.playbackContext && transport.playbackContext->isPlayPending())
            return true;

        if (auto ph = getNodePlayHead())
            return ph->isPlaying();

        return false;
    }

Is it the final return or the ph->isPlaying()?

1 Like

Hi, just saw this and checked, yes, indeed it is ph->isPlaying()!

Ok, if you step into that, does it go in to PlayHead::isPlaying()?
Presumably that’s returning false.

If that’s the case, can you put a breakpoint in PlayHead::stop() and see when that’s hit?

1 Like

Done:

Three different calls to stop, after I’ve actually called transport.start(false):

tracktion::graph::PlayHead::stop() tracktion_PlayHead.h:221
tracktion::engine::EditPlaybackContext::stop() tracktion_EditPlaybackContext.cpp:1162
tracktion::engine::synchroniseEditPosition(tracktion::engine::Edit &, const juce::AudioPlayHead::CurrentPositionInfo &) tracktion_ExternalPlayheadSynchroniser.cpp:91
tracktion::engine::ExternalPlayheadSynchroniser::synchronise(juce::AudioPlayHead &) tracktion_ExternalPlayheadSynchroniser.cpp:191
tracktion::engine::ExternalPlayheadSynchroniser::synchronise(juce::AudioProcessor &) tracktion_ExternalPlayheadSynchroniser.cpp:203
AudioPluginAudioProcessor::processBlock(juce::AudioBuffer<…> &, juce::MidiBuffer &) PluginProcessor.cpp:31
juce::AudioProcessorPlayer::audioDeviceIOCallbackWithContext(const float *const *, int, float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_AudioProcessorPlayer.cpp:319
juce::StandalonePluginHolder::audioDeviceIOCallbackWithContext(const float *const *, int, float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_StandaloneFilterWindow.h:656
juce::StandalonePluginHolder::CallbackMaxSizeEnforcer::audioDeviceIOCallbackWithContext(const float *const *, int, float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_StandaloneFilterWindow.h:510
juce::AudioDeviceManager::audioDeviceIOCallbackInt(const float *const *, int, float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_AudioDeviceManager.cpp:1020
juce::AudioDeviceManager::CallbackHandler::audioDeviceIOCallbackWithContext(const float *const *, int, float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_AudioDeviceManager.cpp:91
juce::CoreAudioClasses::AudioIODeviceCombiner::inputAudioCallback(const float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_CoreAudio_mac.cpp:1806
juce::CoreAudioClasses::AudioIODeviceCombiner::DeviceWrapper::audioDeviceIOCallbackWithContext(const float *const *, int, float *const *, int, int, const juce::AudioIODeviceCallbackContext &) juce_CoreAudio_mac.cpp:1990
juce::CoreAudioClasses::CoreAudioInternal::audioCallback(const AudioTimeStamp *, const AudioTimeStamp *, const AudioBufferList *, AudioBufferList *) juce_CoreAudio_mac.cpp:813
juce::CoreAudioClasses::CoreAudioInternal::audioIOProc(unsigned int, const AudioTimeStamp *, const AudioBufferList *, const AudioTimeStamp *, AudioBufferList *, const AudioTimeStamp *, void *) juce_CoreAudio_mac.cpp:1146

Then the second one:

tracktion::graph::PlayHead::stop() tracktion_PlayHead.h:221
tracktion::engine::EditPlaybackContext::clearNodes() tracktion_EditPlaybackContext.cpp:641
tracktion::engine::EditPlaybackContext::releaseDeviceList() tracktion_EditPlaybackContext.cpp:526
tracktion::engine::EditPlaybackContext::ScopedDeviceListReleaser::ScopedDeviceListReleaser(tracktion::engine::EditPlaybackContext &, bool) tracktion_EditPlaybackContext.cpp:476
tracktion::engine::EditPlaybackContext::ScopedDeviceListReleaser::ScopedDeviceListReleaser(tracktion::engine::EditPlaybackContext &, bool) tracktion_EditPlaybackContext.cpp:471
tracktion::engine::DeviceManager::prepareToStart() tracktion_DeviceManager.cpp:1658
tracktion::engine::DeviceManager::PrepareToStartCaller::handleAsyncUpdate() tracktion_DeviceManager.cpp:167
juce::AsyncUpdater::AsyncUpdaterMessage::messageCallback() juce_AsyncUpdater.cpp:46
juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93
juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104
juce::MessageQueue::runLoopSourceCallback(void *) juce_MessageQueue_mac.h:112
juce::MessageManager::runDispatchLoop() juce_MessageManager_mac.mm:347
juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277
juce::JUCEApplicationBase::main(int, const char **) juce_ApplicationBase.cpp:255
main juce_audio_plugin_client_Standalone.cpp:234

And the third:

tracktion::graph::PlayHead::stop() tracktion_PlayHead.h:221
tracktion::engine::TransportControl::PlayHeadWrapper::stop() tracktion_TransportControl.cpp:589
tracktion::engine::TransportControl::performStop() tracktion_TransportControl.cpp:1676
tracktion::engine::TransportControl::TransportState::valueTreePropertyChanged(juce::ValueTree &, const juce::Identifier &) tracktion_TransportControl.cpp:293
::operator()(juce::ValueTree::Listener &) const juce_ValueTree.cpp:107
juce::ListenerList::callCheckedExcluding<…>(juce::ValueTree::Listener *, const juce::ListenerList<…>::DummyBailOutChecker &, &) juce_ListenerList.h:271
juce::ListenerList::callExcluding<…>(juce::ValueTree::Listener *, &) juce_ListenerList.h:203
juce::ValueTree::SharedObject::callListeners<…>(juce::ValueTree::Listener *, ) const juce_ValueTree.cpp:93
juce::ValueTree::SharedObject::callListenersForAllParents<…>(juce::ValueTree::Listener *, ) const juce_ValueTree.cpp:101
juce::ValueTree::SharedObject::sendPropertyChangeMessage(const juce::Identifier &, juce::ValueTree::Listener *) juce_ValueTree.cpp:107
juce::ValueTree::SharedObject::setProperty(const juce::Identifier &, const juce::var &, juce::UndoManager *, juce::ValueTree::Listener *) juce_ValueTree.cpp:145
juce::ValueTree::setPropertyExcludingListener(juce::ValueTree::Listener *, const juce::Identifier &, const juce::var &, juce::UndoManager *) juce_ValueTree.cpp:780
juce::ValueTree::setProperty(const juce::Identifier &, const juce::var &, juce::UndoManager *) juce_ValueTree.cpp:770
juce::CachedValue::setValue(const bool &, juce::UndoManager *) juce_CachedValue.h:263
juce::CachedValue::operator=(const bool &) juce_CachedValue.h:253
tracktion::engine::TransportControl::TransportState::stop(bool, bool, bool) tracktion_TransportControl.cpp:222
tracktion::engine::TransportControl::stop(bool, bool, bool) tracktion_TransportControl.cpp:904
tracktion::engine::TransportControl::timerCallback() tracktion_TransportControl.cpp:1075
juce::timer::TimerThread::callTimers() juce_Timer.cpp:170
juce::timer::TimerThread::CallTimersMessage::messageCallback() juce_Timer.cpp:271
juce::MessageQueue::deliverNextMessage() juce_MessageQueue_mac.h:93
juce::MessageQueue::runLoopCallback() juce_MessageQueue_mac.h:104
juce::MessageQueue::runLoopSourceCallback(void *) juce_MessageQueue_mac.h:112
juce::MessageManager::runDispatchLoop() juce_MessageManager_mac.mm:347
juce::JUCEApplicationBase::main() juce_ApplicationBase.cpp:277
juce::JUCEApplicationBase::main(int, const char **) juce_ApplicationBase.cpp:255
main juce_audio_plugin_client_Standalone.cpp:234

Ok, you’re running a plugin but not actually running the “host” transport.
You either need to start that (which I don’t think you can do with the standalone?) or no use the ExternalPlayheadSynchroniser to synchronise the transport with the host’s play head.

2 Likes

Argh. And I’ve had this before with standalone builds a while back and forgotten about it.

Thank you for checking Dave!