[Bug]: Simple TE v3.0 program hanging on "Creating Default Controllers..."
Detailed steps on how to reproduce the bug
On macOS, go to https://github.com/debrisapron/tracktion-engine-hello-world/tree/upgrade-tracktion_engine and follow instructions in README. Should build successfully, print the block size & samplerate, then hang when it gets to "Creating Default Controllers...". This is against the v3.0 tag but I've also checked against the latest develop & same error. Note that I don't have any loopbacks or other exotic drivers installed.
What is the expected behaviour?
It should play the demo, as it does with the previous version.
Unit test to reproduce the error?
No response
Operating systems
macOS
What versions of the operating systems?
Sonoma 14.2.1 on M1 Max
Architectures
ARM
Stacktrace
No response
Plug-in formats (if applicable)
No response
Plug-in host applications (DAWs) (if applicable)
No response
Testing on the develop branch
The bug is present on the develop branch
Code of Conduct
- [X] I agree to follow the Code of Conduct
I just tried this on macOS and got the following:
[100%] Built target TracktionHelloWorld
Finding MIDI I/O
MIDI output: IAC Driver Bus 1 (enabled)
opening MIDI out device:IAC Driver Bus 1
MIDI input: IAC Driver Bus 1 (enabled)
opening MIDI in device: IAC Driver Bus 1
Audio block size: 512 Rate: 44100
Rebuilding Wave Device List...
Wave In: New Label (enabled): 0 (L)
Wave Out: Output 1 + 2 (enabled): 0 (L), 1 (R)
Default Wave Out: Output 1 + 2
Default MIDI Out: IAC Driver Bus 1
Default Wave In: New Label
Default MIDI In: IAC Driver Bus 1
Creating Default Controllers...
but it was playing the audio.
I'm not quite sure what the reported problem is here? It looks like Creating Default Controllers is just the last message to be logged?
But have you run this in debug? You'll get assertions because you've not actually started the JUCE MessageManager.
Tracktion Engine requires this to be running and you're not really supposed to block the message thread as there could be all kinds of async messages piling up behind your blocking while loop.
Huh that's interesting, was definitely not producing sound for me but good to know it's not just hanging. I actually thought I was running this in debug but looking at the code I forgot to put it in lol. To the point about the blocking while what else should I do here? I found that without it the program would simply exit before any audio had been emitted, but I'm sure there's a standard construct I should be using here. This is very much a learning process for me!
Anyway thanks for the reply! Will do a bit more investigation & probably close this soon.
It's a bit tricky as you really want to be creating a juce::JUCEApplication rather than a console app.
However, you might get away with adding a ScopedJuceInitialiser_GUI instance at the top of your main function (see our TestRunner.h for an example) and then manually run the message loop afterwards instead of your while loop:
JUCE_TRY
{
// loop until a quit message is received..
MessageManager::getInstance()->runDispatchLoop();
}
JUCE_CATCH_EXCEPTION
Hello again! Sorry, I let this hang for a while, but trying to get it working again with TE V3. So I followed your suggestion and added the lines you mentioned, now my main.cpp looks like this:
#include <tracktion_engine/tracktion_engine.h>
#include <memory>
using namespace std;
namespace te = tracktion;
using namespace std::literals;
using namespace te::literals;
static void addNoteToClip(
te::MidiClip *midiClip,
int noteNumber,
int velocity,
te::BeatPosition start,
te::BeatDuration duration)
{
midiClip->getSequence().addNote(
noteNumber,
start,
duration,
velocity,
0,
nullptr);
}
int main()
{
juce::ScopedJuceInitialiser_GUI init;
// Create the engine
te::Engine engine{"Tracktion Hello World"};
// Create an edit
auto edit = std::make_unique<te::Edit>(
engine,
te::Edit::forEditing);
// Create a track
edit->ensureNumberOfAudioTracks(1);
auto track = te::getAudioTracks(*edit)[0];
// Get length of 1 bar
const tracktion::TimeRange oneBarTimeRange(
0s,
edit->tempoSequence.toTime({1, tracktion::BeatDuration()}));
// Insert a 1 bar long Midi clip
auto clip = track->insertNewClip(
te::TrackItem::Type::midi,
"Midi Clip",
oneBarTimeRange,
nullptr);
auto midiClip = static_cast<te::MidiClip *>(clip);
// Add a 4-note C-E-G-C sequence to the clip
// Note the use of Tracktion's beat position/duration literals
addNoteToClip(midiClip, 60, 100, 0_bp, 0.5_bd);
addNoteToClip(midiClip, 64, 100, 1_bp, 0.5_bd);
addNoteToClip(midiClip, 67, 100, 2_bp, 0.5_bd);
addNoteToClip(midiClip, 72, 100, 3_bp, 0.5_bd);
// Create a built-in synth plugin instance to play the sequence on
auto plugin = edit->getPluginCache()
.createNewPlugin(te::FourOscPlugin::xmlTypeName, {})
.get();
auto fourOscPlugin = static_cast<te::FourOscPlugin *>(plugin);
// Insert the plugin to the track
track->pluginList.insertPlugin(*fourOscPlugin, 0, nullptr);
// Get the transport & set it to the start of the edit
auto &transport = edit->getTransport();
transport.setPosition(0s);
// Set the transport to loop our clip
transport.setLoopRange(clip->getEditTimeRange());
transport.looping = true;
// Begin playback
transport.play(false);
// loop until a quit message is received..
juce::MessageManager::getInstance()->runDispatchLoop();
}
This builds and runs fine, but all it does is print out the following & then immediately exits:
Audio block size: 512 Rate: 44100
Creating Default Controllers...
Cleaning up temp files...
For the sake of completeness here is my CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(TRACKTION_HELLO_WORLD VERSION 0.0.1)
add_subdirectory(tracktion_engine/modules/juce)
add_subdirectory(tracktion_engine/modules)
juce_add_console_app(TracktionHelloWorld
PRODUCT_NAME "Tracktion Hello World")
target_sources(TracktionHelloWorld
PRIVATE
main.cpp)
target_compile_definitions(TracktionHelloWorld
PRIVATE
JUCE_WEB_BROWSER=0
JUCE_USE_CURL=0)
target_link_libraries(TracktionHelloWorld
PRIVATE
tracktion::tracktion_engine
juce::juce_core
PUBLIC
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)
set_property(TARGET TracktionHelloWorld PROPERTY CXX_STANDARD 20)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(TracktionHelloWorld PRIVATE "-latomic")
endif()
I know I could probably get it working if I just change it to a regular JUCE GUI App, but I'd really like to boil it down to the fewest possible moving parts if I can! Also I have an interest in using TE for a headless audio app and it would be great to know if this is actually possible. Thanks in advance if you have any thoughts!
Is it actually quitting?
If so, is a quit message being posted to the message queue?
You should be able to put a breakpoint in the juce::application/message_manager classes to see where that is coming from.
OK this is good stuff! So I registered an ActionListener that just prints any received messages to stdout and it never receives anything! Which is super weird because runDispatchLoop should block forever if it doesn't get a quit message right?
I added a bunch of logging and it's not silently crapping out early or anything. It runs all the way through to the end and returns. So runDispatchLoop isn't blocking at all.
So it's working as you hoped?
No, because it doesn't play audio and exits immediately. With TE 2 it plays audio endlessly until I kill it, now it just exits without playing anything.
Right, but you must be able to debug why it's quitting?
But a breakpoint in MessageManager::runDispatchLoop() and step through it. Is the quitMessageReceived equal to 1?
If so, look at the line quitMessageReceived = true, is that being hit at all? Or MessageManager::stopDispatchLoop() being hit?
This isn't really a supported use case so I can't spend a bunch of time debugging it myself I'm afraid but I'm happy to help if you can debug it and find out what's actually happening. But you'll need to be able to use the debugger and breakpoints etc. to step in to the juce code.
OK will try breakpointing it, thanks!
I bet you thought this would be clogging up your issues forever? Nope, I'm back for another round!
So I decided to rework my "Hello World" as a simple GUI app, since it seems like TE isn't really intended for headless use. I've created a dead-simple Juce GUI app & put my TE code in the MainComponent. Here's what I've got now:
#include <juce_gui_extra/juce_gui_extra.h>
// ---- INTERESTING STUFF STARTS HERE ----
#include <tracktion_engine/tracktion_engine.h>
#include <memory>
using namespace std;
namespace te = tracktion;
using namespace std::literals;
using namespace te::literals;
static void addNoteToClip(
te::MidiClip *midiClip,
int noteNumber,
int velocity,
te::BeatPosition start,
te::BeatDuration duration)
{
midiClip->getSequence().addNote(
noteNumber,
start,
duration,
velocity,
0,
nullptr);
}
//==============================================================================
class MainComponent final : public juce::Component
{
public:
MainComponent(te::Engine &e) : engine(e)
{
DBG("* Initialize MainComponent");
setSize(400, 200);
DBG("* Create an edit");
edit = std::make_unique<te::Edit>(
engine,
te::Edit::forEditing);
DBG("* Create a track");
edit->ensureNumberOfAudioTracks(1);
auto track = te::getAudioTracks(*edit)[0];
DBG("* Get length of 1 bar");
const tracktion::TimeRange oneBarTimeRange(
0s,
edit->tempoSequence.toTime({1, tracktion::BeatDuration()}));
DBG("* Insert a 1 bar long Midi clip");
auto clip = track->insertNewClip(
te::TrackItem::Type::midi,
"Midi Clip",
oneBarTimeRange,
nullptr);
auto midiClip = static_cast<te::MidiClip *>(clip);
DBG("* Add a 4-note C-E-G-C sequence to the clip");
// Note the use of Tracktion's beat position/duration literals
addNoteToClip(midiClip, 60, 100, 0_bp, 0.5_bd);
addNoteToClip(midiClip, 64, 100, 1_bp, 0.5_bd);
addNoteToClip(midiClip, 67, 100, 2_bp, 0.5_bd);
addNoteToClip(midiClip, 72, 100, 3_bp, 0.5_bd);
DBG("* Create a built-in synth plugin instance to play the sequence on");
auto plugin = edit->getPluginCache()
.createNewPlugin(te::FourOscPlugin::xmlTypeName, {})
.get();
auto fourOscPlugin = static_cast<te::FourOscPlugin *>(plugin);
DBG("* Insert the plugin to the track");
track->pluginList.insertPlugin(*fourOscPlugin, 0, nullptr);
DBG("* Get the transport & set it to the start of the edit");
auto &transport = edit->getTransport();
transport.setPosition(0s);
DBG("* Set the transport to loop our clip");
transport.setLoopRange(clip->getEditTimeRange());
transport.looping = true;
DBG("* Begin playback");
transport.play(false);
}
void paint(juce::Graphics &g) override
{
g.fillAll(juce::Colours::darkgrey);
}
private:
te::Engine &engine;
std::unique_ptr<te::Edit> edit;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};
// ---- BORING JUCE BOILERPLATE FOLLOWS ----
//==============================================================================
class TEHelloWorldApplication : public juce::JUCEApplication
{
public:
TEHelloWorldApplication() {}
const juce::String getApplicationName() override { return JUCE_APPLICATION_NAME_STRING; }
const juce::String getApplicationVersion() override { return JUCE_APPLICATION_VERSION_STRING; }
void initialise(const juce::String &commandLine) override
{
mainWindow.reset(new MainWindow(getApplicationName()));
}
void shutdown() override
{
mainWindow = nullptr;
}
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow(juce::String name)
: DocumentWindow(name,
juce::Desktop::getInstance().getDefaultLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId),
DocumentWindow::allButtons)
{
setUsingNativeTitleBar(true);
te::Engine engine{JUCE_APPLICATION_NAME_STRING};
setContentOwned(new MainComponent(engine), true);
setVisible(true);
}
void closeButtonPressed() override
{
JUCEApplication::getInstance()->systemRequestedQuit();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
};
private:
std::unique_ptr<MainWindow> mainWindow;
};
//==============================================================================
START_JUCE_APPLICATION(TEHelloWorldApplication)
This builds & runs successfully and prints the following output:
[boring build log omitted]
JUCE v8.0.6
Settings file: /Users/ms/Library/TEHelloWorld/Settings.xml
Audio block size: 512 Rate: 44100
Creating Default Controllers...
* Initialize MainComponent
* Create an edit
Edit loaded in: 2 ms
* Create a track
* Get length of 1 bar
* Insert a 1 bar long Midi clip
* Add a 4-note C-E-G-C sequence to the clip
* Create a built-in synth plugin instance to play the sequence on
* Insert the plugin to the track
* Get the transport & set it to the start of the edit
* Set the transport to loop our clip
* Begin playback
JUCE Assertion failure in tracktion_DeviceManager.cpp:432
Cleaning up temp files..
JUCE Assertion failure in tracktion_Engine.cpp:152
./build-and-run.sh: line 3: 41355 Segmentation fault: 11 ./cmake-build/TEHelloWorld_artefacts/Debug/TEHelloWorld.app/Contents/MacOS/TEHelloWorld
As you can see there are two assertion failures in TE here. To save you looking them up the first one is:
jassert (activeContexts.isEmpty());
And the second one is:
jassert (deviceManager != nullptr);
This is as far as I've got for now! Can you point me in a direction of investigation, ideally one that doesn't involve attaching a debugger, because I've already lost a lot of time going down that rabbit hole. Thank you!
FWIW a big project I've been working on recently uses the engine headlessly, so it's well tested like that. Yes, you need an event loop, but you don't need a GUI.
If you're hitting assertions like that, then it's probably because you're shutting down the engine before deleting things that are using it.
But github issues are for bugs, not coding advice, so I'm going to close this. If you do find an actual bug, please open a specific ticket for it.
For general "how do I do this" advice, a better place would be the discord: https://discord.gg/cVQhcrxX