Specify working directory when loading a .tracktionedit containing relative paths

I have a .tracktionedit file I want to load into a te::Edit. The file was saved with te::EditFileOperations(*edit).save(...). I want to load the same file with te::loadEditFromFile, like this:

    std::unique_ptr<te::Edit> edit;
    te::Edit::LoadContext loadContext;
    if (editFile.existsAsFile()) {
        edit = std::make_unique<te::Edit>(
            engine,
            te::loadEditFromFile(editFile, te::ProjectItemID::createNewID(0)),
            te::Edit::forRendering, &loadContext, 0);
    }

The code above throws an exception found in juce_File.cpp. The exception is only thrown when the .tracktionedit file includes a relative path.

            /*  When you supply a raw string to the File object constructor, it must be an absolute path.
                If you're trying to parse a string that may be either a relative path or an absolute path,
                you MUST provide a context against which the partial path can be evaluated - you can do
                this by simply using File::getChildFile() instead of the File constructor. E.g. saying
                "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute
                path if that's what was supplied, or would evaluate a partial path relative to the CWD.
            */
            jassertfalse;

The edit contains a relative path to a .wav file .in an audio clip. Normally I could specify a custom filePathResolver on the Edit instance, but in this case the edit is not yet constructed, so there is no opportunity to set a custom filePathResolver.

As per Project info and why not to use it atm, I am avoiding the ProjectManager. How can I specify relative path behavior for loading an edit file?

I see. I think we need to modify the Edit constructor so you can pass in your resolvers to avoid this. It on my list to make some changes like this so rather than having a number of arguments to Edit (...) there’s a single struct called something like Edit::InitialisationOptions.

I’ll bump that up the priority list.


For now, if you just continue past the assertion and then set the editFileRetriever afterward construction, does it work for you? You may need to call edit.getTransport().editHasChanged() to rebuild the audio graph in this case for the changes to take effect.

Thanks @dave96 , passing in the edit editFileRetriever in an init options struct would work great for my purposes.

In my case, the edit is created with the te::Edit::forRendering role. My understanding is that this implies:

  • Edit playback is disabled
  • The edit contains no playbackContext
  • edit->getTransport().ensureContextAllocated() does not actually allocate playback context
  • edit->getTransport().editHasChanged() does not actually update the graph.

I can skip over the assertions, but cannot actually render the edit. Is there an equivalent of editHasChanged() for use with rendering as opposed to playback?

Currently, when I try to render the edit (when the edit has a clip with a relative path) the render spins indefinitely, because the WaveAudioNode never passes its isReadyToRender() check. This happens even though I have set a custom editFileRetriever after construction.

Yes, all of your statements are correct.

Do you know why WaveAudioNode::isReadyToRender is returning false?
Can you put a breakpoint in there and see what the audioFile member is? Is it a valid file? Or is it empty? (In which case this method should just bail out).

Setting the editFileRetriever after Edit construction should be ok if you’re rendering as the AudioNode graph will only get created when you start to render it.

WaveAudioNode::isReadyToRender returns false on this line:

    if (audioFileSampleRate == 0.0 && ! updateFileSampleRate())
        return false;

(the sample rate is 0)

I noticed the audio file is resolving the relative path incorrectly. This is the only audio clip in the .tracktionedit file:

    <AUDIOCLIP name="My Clip" start="0.0" length="60.0" offset="0.0"
               id="1004" source="..\..\..\..\reaper\tone.wav" sync="0"
               elastiqueMode="0" pan="0.0" colour="ffff0000">
      <LOOPINFO rootNote="-1" numBeats="56.12213151927438" oneShot="0" denominator="4"
                numerator="4" bpm="0.0" inMarker="0" outMarker="-1"/>
    </AUDIOCLIP>

However inside the WaveAudioNode::isReadyToRender() function, audioFile points to this location: C:\reaper\tone.wav. The correct path would be: C:\projects\reaper\tone.wav (this is where the file actually is, AND where the AUDIOCLIP source xml attribute actually points).

Are you using the default filePathResolver (in the Edit constructor)?

If so, I presume it’s this line that’s incorrect:

        if (editFileRetriever)
            return editFileRetriever().getSiblingFile (path);

But I can’t see why as with no relative path e.g. source="tone.wav", you’d expect this to look for a file alongside your Edit file.

Any idea which bit of the resolution is going wrong?

Yes, I’m using the default filePathResolver in the constructor. I think I know why resolution fails-- and it’s simpler than I thought.

When Edit is first constructed, file resolution fails (because there is currently no way to specify custom revolvers during construction). I skip over the assertion failure as you recommend.

Next I set a custom editFileRetriever and call edit->getTransport().editHasChanged(); This does not rebuild the graph or resolve relative files names (possibly because the edit role is set to te::Edit::forRendering).

I then call te::Renderer::renderToFile(...) which does not trigger file path re-resolution using the custom filePathResolver - it just uses the existing resolutions from the initial construction, so the rendering graph is built, but the filenames were never updated.

Does that sound right? If so, I guess the question is - before rendering an edit, is there a way to re-resolve file names, using the custom editFileRetriever? If not it’s probably easiest to hack the source for now, and then wait for the patch to supply custom resolver/retrievers to the Edit constructor.

If that doesn’t sound right, perhaps I should open an github issue with a code example so it’s easier to troubleshoot. Let me know, and thank you for your help!

Ok, that makes sense. I’ll add the custom editFileRetriever to the to-do list.

One final thing that might work is calling sourceMediaChanged() on the audio clips in your Edit before attempting to render. That should refresh their currentSourceFile member which is what is used to create the audio graph.

Skipping over the breakpoint and calling sourceMediaChanged() does cause the file to be resolved correctly. Thank you you for your help!

Sorry for the delayed response but the Edit::Options struct for constructing an Edit should now allow you to do what you need.

1 Like

Huzzah! Thanks a lot.
I Looked for info on the release cycle, but couldn’t find any. How often will development branch be merged into master?

We don’t have a hard and fast rule about this. We tend to make changes on develop so people can test them out and if there are no issues and the branch has been relatively stable for a period of a couple of weeks we’ll merge it in to master.

I guess this is similar to JUCE in that regard.
To be honest though, I’d recommend just using the develop branch if you want all the latest changes.

1 Like