I ended up creating a background thread loader class that inherits juce::Thread
and juce::AsyncUpdater
so I could load the .tracktionedit
file in the background, to avoid the UI taking a huge hit during loading on the message thread. The background loader class looks like this:
//==============================================================================
/* Used to load a Tracktion edit in a background thread.
*/
class TemplateEditLoader : public juce::Thread, public juce::AsyncUpdater
{
public:
TemplateEditLoader(PrismPlayer& p) : juce::Thread("Prism Player Template Loader"), player(p) {}
void load(const juce::File file, bool playOnLoad = true, const juce::String projectPath = {})
{
autoPlay = playOnLoad;
editFile = file;
projectFilePath = projectPath;
jassert(editFile.existsAsFile());
startThread();
}
void run() override
{
player.templateEdit = te::loadEditFromFile(player.engine, editFile);
triggerAsyncUpdate();
}
void handleAsyncUpdate() override
{
player.finalizeEditTemplateAsync(autoPlay, projectFilePath);
}
private:
PrismPlayer& player;
juce::File editFile;
juce::String projectFilePath = {};
bool autoPlay = true;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TemplateEditLoader)
};
The player is a custom class that hosts the te::Engine
instance, the edit, and wraps interaction with the loaded edit, which is specific to my project. It has an instance of the background loader class as a member, and calls load()
to load a .tracktionedit
file.
The main edit file is loaded during run()
but some post-load initialization needs to take place back on the message thread, so triggerAsyncUpdate()
is called, which invokes handleAsyncUpdate()
- this in turn calls a method on the player that does some post-load init:
// Below is a section of player.finalizeEditTemplateAsync() called async after the edit loads in the background thread
...
// Finish Loading the edit
jassert(templateEdit != nullptr);
auto& transport = templateEdit->getTransport();
transport.setLoopRange({ 0.0, templateEdit->getLength() });
transport.looping = true;
transport.ensureContextAllocated();
// If a project file exists
if (projectPath.isNotEmpty())
{
projectFile = File(projectPath);
if (!projectFile.existsAsFile())
{
juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::WarningIcon, "Project File Error", "Project file not found:\n" + projectPath);
return;
}
auto project = projectManager.createNewProject(projectFile);
// go through all clips with source files
for (auto clip : findAllClipsWithSourceFiles(*templateEdit))
{
DBG("Track: " + clip->getTrack()->getName()+ ", Clip: " + clip->getName());
// get the source file's ProjectItemID and get the ProjectItem from the associated project...
auto& sourceRef = clip->getSourceFileReference();
auto pID = sourceRef.getSourceProjectItemID();
if (auto pItem = project->getProjectItemForID(pID))
{
// update the edit's source file using the project's file path
auto sFilePath = pItem->getRawFileName();
// Was this a relative path?
auto sFile = projectFile.getParentDirectory().getChildFile(sFilePath);
jassert(sFile.existsAsFile());
sourceRef.setToDirectFileReference(sFile, true);
}
}
projectManager.clearProjects();
}
...
Those should be the most relevant bits.
If your edit file uses time stretching on any audio clips you’ll need to enable that when you build your TE project or you’ll get the time stretch error I mentioned earlier in the thread.
If you want to use your plug-ins correctly in your TE project, make sure they are on your enabled plug-ins list or they won’t load in your TE project/app:
// Reference the TE plugin manager
auto& pluginManager = engine.getPluginManager();
// Get the known plugin list
auto& knownPluginList = engine.getPluginManager().knownPluginList;
You can add them programmatically through the known plug-in list as seen in the code above, or you can use the build-in UI, similar to AudioPluginHost, which will save an xml settings file for your app with the allowed plug-ins list. Below is a utility function I used to let the user edit that list:
void PlayerToolbar::showPluginList()
{
auto& engine = player.getEngine();
DialogWindow::LaunchOptions o;
o.dialogTitle = TRANS("Plugins");
o.dialogBackgroundColour = Colours::black;
o.escapeKeyTriggersCloseButton = true;
o.useNativeTitleBar = true;
o.resizable = true;
o.useBottomRightCornerResizer = true;
auto v = new PluginListComponent(engine.getPluginManager().pluginFormatManager,
engine.getPluginManager().knownPluginList,
engine.getTemporaryFileManager().getTempFile("PluginScanDeadMansPedal"),
te::getApplicationSettings());
v->setSize(800, 600);
o.content.setOwned(v);
o.launchAsync();
}
FWIW, this was all done before Tracktion Engine 2.0 release, so I’m not sure if there are any breaking changes to get this all to work with 2.0.
I also had to do a bit more custom stuff to be able to open and render plug-in windows within my TE app. DM me if you need some code examples of that, as it’s a bit much to post in the thread.
Cheers.