Simple Tracktion Engine v3 example not producing sound

Hi JUCErs,

A while back I created a Tracktion Engine Hello World project intended as a least-possible-code TE app that actually produces audio. This worked great with TE v2, but when I upgraded it to TE v3 it stopped making any sound (for me at least, I had a report from another user of it working for them). After being advised by the TE devs that “this use case isn’t really supported” I slightly refactored the app from it’s original console-only flavor to incorporate a super-simple GUI, however this has failed to make any difference whatsoever. The app compiles and runs, but it hits TE assertions that mean nothing to me (note: I would describe myself as an “intermediate” C++ developer, and a JUCE n00b), then crashes.

Here’s the current code, will be cleaned up further once it’s working:

#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)

And for completeness, here’s CMakeLists.txt:

cmake_minimum_required(VERSION 3.22)

project(TE_HELLO_WORLD VERSION 0.0.2)

add_subdirectory(tracktion_engine/modules/juce)
add_subdirectory(tracktion_engine/modules)

juce_add_gui_app(TEHelloWorld
    PRODUCT_NAME "TEHelloWorld")

target_sources(TEHelloWorld
    PRIVATE
        Main.cpp)

target_compile_definitions(TEHelloWorld
    PRIVATE
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
        JUCE_APPLICATION_NAME_STRING="$<TARGET_PROPERTY:TEHelloWorld,JUCE_PRODUCT_NAME>"
        JUCE_APPLICATION_VERSION_STRING="$<TARGET_PROPERTY:TEHelloWorld,JUCE_VERSION>")

target_link_libraries(TEHelloWorld
    PRIVATE
        tracktion::tracktion_engine
        juce::juce_gui_extra
        #juce::juce_audio_utils
    PUBLIC
        juce::juce_recommended_config_flags
        juce::juce_recommended_lto_flags
        juce::juce_recommended_warning_flags)

set_property(TARGET TEHelloWorld PROPERTY CXX_STANDARD 20)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  target_link_libraries(TEHelloWorld PRIVATE "-latomic")
endif()

Running this results in the following console 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);. So that’s where I’ve got to.

I suspect that this problem is something to do with me not understanding object lifetimes properly, but beyond that I have no clue. And tbh I feel like this is just an example of a larger problem I have, which is that I have no way of figuring out TE-related bugs except spamming you lovely people. With plain JUCE, I can google my problems and almost always find some pretty good advice, but as soon as TE enters the picture the process becomes incredibly painful. Anyway, maybe that’s a me problem…

You are creating the Tracktion Engine as a local variable here, which goes out of scope and will be destroyed, and you are passing a pointer to that into the constructor of the MainComponent, so that’s not going to work :

You should instead make the engine exist for as long as there are pointers/references to it somewhere. (That is, make it a member variable of some suitable class, don’t use a global/static variable, those are almost certainly going to cause other problems.)

1 Like

OK, this is brilliant, thanks xenakios! So instead of passing in the engine from MainWindow, I’ve just initialized it as a private member variable of MainComponent. Now the declaration looks like:

private:
    te::Engine engine{JUCE_APPLICATION_NAME_STRING};

…which now I look at it seems obviously correct lol. And the failed assertions are gone! HOWEVER, even though the program runs fine, it still produces no audio. Here’s the console output:

[...same output as before...]
* Begin playback
MIDI Input devices scanned in: 12 ms
Updating MIDI I/O devices
Found MIDI in: all_midi_in ("All MIDI Ins") (enabled)
Rebuilding Wave Device List...
Wave In: Input 1 (enabled): 0 (L)
Wave Out: Output 1 + 2 (enabled): 0 (L), 1 (R)
Default Wave Out: Output 1 + 2
Default MIDI Out: 
Default Wave In: Input 1
Default MIDI In: All MIDI Ins
Generating waves: 224ms

But if I revert it to use TE v2, everything works and audio is produced! So more and more I’m thinking that TE v3 requires some new bit of configuration that I’m not aware of, but I’m buggered if I can find it. Any ideas? FYI this is on MacOS Sonoma, M1 Max.

You appear to be having the same problem with some other objects too, you are creating those as local variables that will be destroyed at the end of the MainComponent constructor. (OTOH I am not completely sure how the Tracktion Engine should behave with all those.)

OK my declarations section now looks like this:

private:
    te::Engine engine{JUCE_APPLICATION_NAME_STRING};
    std::unique_ptr<te::Edit> edit;
    tracktion::engine::AudioTrack *track;
    tracktion::engine::Clip *clip;
    tracktion::engine::Plugin *plugin;

But as far as I can tell it hasn’t made any difference. Same console output, same silence.

I’d love to know your thoughts about what sort of problems are going to occur as a result of global/static variable use for holding TE-related objects .. is there some advice you can give here that would clarify this?

For my part, I’ve built a TE-based application for internal use, and found that I had to have globals (albeit in a GlobalClass) organized properly to make the app functional. But I’d love to learn more about how this should properly be organized to build a more bullet-proof TE-based app .. what are the best practices here?