My Tracktion Engine Python bindings crash Python =/

I’m making Python bindings for Tracktion Engine using PyBind11, and when I try to create an AudioTrack instance in Python, Python just quits. No error message. It does this both inside a .py program in in the interactive prompt. I’m not sure how to debug this..

Here’s my C++ code:

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "tracktion_engine/tracktion_engine.h"

namespace py = pybind11;
using namespace tracktion::engine;

PYBIND11_MODULE(te_py_bind, m) {
  m.doc() = "Python bindings for Tracktion Engine, a framework for building DAW-like audio applications.";

  Engine engine{ juce::String("Soundshop") };
  Edit edit{ engine, Edit::EditRole::forEditing };
  TransportControl& transport{ edit.getTransport() };
  juce::ValueTree edittree("_");
  
 /* // ValueTree class binding (used for Edit state)
  py::class_<juce::ValueTree>(m, "ValueTree")
    .def(py::init<juce::Identifier>(), py::arg("type"), "Creates a ValueTree with the specified type.")
    .def("set_property", &juce::ValueTree::setProperty, py::arg("name"), py::arg("value"), py::arg("undo_manager") = nullptr,
      "Sets a property on the ValueTree.");
*/
  /*// Track class binding
  py::class_<AudioTrack>(m, "AudioTrack")
    .def(py::init([&edit](const juce::ValueTree& state) {
    return AudioTrack(edit, state);  // provide edit from C++, not Python
      }));
      */
 
 /* py::class_<AudioTrack, std::shared_ptr<AudioTrack>>(m, "AudioTrack")
    .def(py::init([&edit]() {
    juce::ValueTree tree("EditTree");
    return std::make_shared<AudioTrack>(edit, tree);
      }));
      */

  py::class_<AudioTrack, std::shared_ptr<AudioTrack>>(m, "AudioTrack")
    .def(py::init([&edit, &edittree]() {
    return std::make_shared<AudioTrack>(edit, edittree);
      }));

  // Clip class binding
  py::class_<Clip, std::unique_ptr<Clip, py::nodelete>>(m, "Clip")
    .def("get_name", &Clip::getName, "Returns the name of the clip.")
    .def("get_position", &Clip::getPosition, "Returns the position of the clip in seconds.")
    .def("set_position", &Clip::setPosition, py::arg("position"), "Sets the position of the clip in seconds.");

  // DeviceManager class binding
  py::class_<DeviceManager, std::unique_ptr<DeviceManager, py::nodelete>>(m, "DeviceManager")
    .def("initialise", &DeviceManager::initialise, py::arg("num_input_channels"), py::arg("num_output_channels"),
      "Initializes the device manager with the specified number of input and output channels.");

  
      
}

By the way, does Tracktion Engine require you to pass control to some engine that runs for the duration of the application, like for example Qt does? Maybe that’s the problem?


@ibisum I tried putting it into a repo like you suggested so that we can work on making a CMakeLists.txt together, but I can’t figure out how to properly ignore all the files that aren’t necessary. I got the Visual Studio template for .gitignore and put it in my repository, but it still tries to upload several files over 100MB, for example some *.db files, etc. I tried several commands to fix it that I found online, including git reset --soft HEAD, git rm -r --cached ., git clean -xdf, git reset HEAD --hard, then adding, committing, and pushing. I think the Visual Studio .gitignore template just isn’t excluding enough, and I don’t know which files are necessary and which aren’t in order to modify it.

Here are the files it chokes on, but I suspect there are many others it’s trying to upload that aren’t necessary, and that they all add up to way too large a push.

remote: Resolving deltas: 100% (75/75), done.
remote: warning: File te_py_bind/NewProject/Builds/VisualStudio2022/.vs/te_py_bind/CopilotIndices/17.13.444.19527/CodeChunks.db is 74.07 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: warning: File te_py_bind/NewProject/Builds/VisualStudio2022/.vs/te_py_bind/CopilotIndices/17.13.444.19527/SemanticSymbols.db is 91.57 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: warning: File te_py_bind/NewProject/Builds/VisualStudio2022/x64/Debug/Dynamic Library/include_juce_gui_basics.obj is 58.84 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: warning: File te_py_bind/NewProject/Builds/VisualStudio2022/x64/Debug/Dynamic Library/include_tracktion_engine_playback.obj is 52.08 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: error: Trace: 46e163029183a26e1f78f52cd2dcf722cc187fdb4bac6411199849415ff955e9
remote: error: See https://gh.io/lfs for more information.
remote: error: File te_py_bind/NewProject/Builds/VisualStudio2022/x64/Debug/Dynamic Library/NewProject.pdb is 347.38 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: File te_py_bind/NewProject/Builds/VisualStudio2022/.vs/te_py_bind/v17/ipch/AutoPCH/6f43993f8cb4e307/FROMTHEFORUM.ipch is 632.00 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: File te_py_bind/NewProject/Builds/VisualStudio2022/.vs/te_py_bind/v17/Browse.VC.db is 268.87 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: File te_py_bind/NewProject/Builds/VisualStudio2022/x64/Debug/Dynamic Library/NewProject.ilk is 182.29 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: File te_py_bind/NewProject/Builds/VisualStudio2022/.vs/te_py_bind/v17/ipch/AutoPCH/91a60117477fad36/INCLUDE_TRACKTION_ENGINE_PLAYBACK.ipch is 932.25 MB; this exceeds GitHub's file size limit of 100.00 MB

I wouldn’t recommend putting the engine declarations etc inside of the pybind11 macro. I can bet that will cause some kind of object initialization order issue as the object instances will end up as global variables when the macro is expanded. (It is undetermined in what order global variables are initialized in C++.)

I’d recommend adding minimal pybind11 bindings for Engine, Edit and TransportControl and then create the objects in the Python code. Or make a C++ wrapper class for those that you bind for pybind11 and instantiate it in Python.

Another issue indeed may be that Tracktion Engine does require an event loop running. This can be difficult to get working properly in the Python interactive interpreter. (I bumped into this issue myself when hosting Clap plugins, which require the ability to post functions to be run on the main/message thread as soon as possible…) If just running scripts, you can use for example the Juce event loop and block the execution of the Python script while Tracktion Engine needs to be running. I have not yet found a completely satisfactory solution for these problems in my own project.

With those considerations, the usage in a Python script could be something like :

import inhahe

# this would instantiate the c++ object that holds the Tracktion Engine etc needed objects
engine = inhahe.Engine()
# whatever needed to initialize the Edit etc...

# start running the engine, block execution with an event loop
engine.run()
# and can we ever get here...? that's a good question.
# one way would be to somehow catch the Python keyboard interrupt and make that break from the event loop. alternatively, run() could be of form run(5.0),
# which would run the engine/event loop for 5 seconds. or maybe a received MIDI message could stop the  engine.

I originally had the three or four class instantiations up top inside the pybind11 section, and when I tried to load the dll in Python it would say there was a dll initialization problem. After I moved them to inside the pybind11 section, I no longer got that error. But I guess I could try experimenting with having only some of them inside the pybind11 section?

How do I run Tracktion’s event loop? And what functions does it call so that I can do stuff while it’s running?

I probably won’t really need to run Tracktion Engine in the interactive interpreter, I was just trying that for testing purposes. Though maybe that ability would have been nice later on.. =P

I’d recommend adding minimal pybind11 bindings for Engine, Edit and TransportControl and then create the objects in the Python code. Or make a C++ wrapper class for those that you bind for pybind11 and instantiate it in Python.

Originally, I was trying to instantiate Engine in Python, but I couldn’t figure out how to make the wrapper do that. I asked someone for help, and he didn’t know, but he didn’t have much/any experience with PyBind11. I don’t remember what the problem was now, but I remember I got one error if I included an “&” and another error if I didn’t. One way was an attempt at fixing the other way. He said basically that there was no easy way to solve the problem.

Oh, I think it wasn’t the engine I was having trouble with, it was the Edit object. I was trying py::class_<tracktion::engine::Edit::Options>(m, "Options").def(py::init<>()).def_readwrite("engine", &Edit::Options::engine); after TracktionEngine: tracktion::engine::Edit::Options Struct Reference, and VS underlines the ampersand saying “pointer to member of type “tracktion::engine::Engine &” is not allowed”. When I take the ampersand away, it says, “a nonstatic member reference must be relative to a specific object”.