Hi @dave96 ,
How can I change the source reference of an audio file when the original file is moved or deleted from its original location?
I have created a Wave Audio Clip from a file present at a certain location on the file system.
When I move that file the AudioFile linked to the Wave Audio Clip is no longer valid and needs to be referenced again.
Now I have two problems :
How do I know what was the name of the old referenced file? I want to ask the user to search in a specific directory for the file “foo.wav”;
To reference a new file I use the getSourceFileReference().setToProjectFileReference(newFile, true) method; that seems to work but it changes the “source” property which is subject to UndoManager and so when I do Undo I lose the new referencing.
Are you using a te::Project?
If not you probably want to use setToDirectFileReference.
If you’re using a Project, I think you can get the name of the ProjectItem from the ID that was used. Bear in mind though, we don’t really recommend using Projects these days as they’re more difficult to manage. It’s easier to just assume the folder is your project folder and any Edits in that folder are part of the project. Then you can use relative paths for audio files.
Does it not make sense to have it as part of the undo chain? If someone changes a file source to another file, they might want to undo that change?
I think what we do is have a “re-link missing files” operation that works when the Edit is closed so you can update the files without it messing with the undo history.
Yes @dave96 , I am using te::Project and I would not want to change it given the great effort so far.
The ProjectItem associated with ProjectItemID is null so I don’t know how I can get its name as you can see.
Does it not make sense to have it as part of the undo chain? If someone changes a file source to another file, they might want to undo that change?
I think what we do is have a “re-link missing files” operation that works when the Edit is closed so you can update the files without it messing with the undo history.
Exactly, it doesn’t make sense , but if we look at the setToProjectFileReference function :
auto oldFile = getFile();
auto project = edit.engine.getProjectManager().getProject (edit);
if (updateProjectItem)
{
if (auto projectItem = getSourceProjectItem())
{
// if we've got a proper source ProjectItem but its file is missing, reassign the ProjectItem..
if (! projectItem->getSourceFile().existsAsFile())
{
projectItem->setSourceFile (file);
}
else if (project != nullptr)
{
// see if there's another one that has this new file..
if (auto existingItem = project->getProjectItemForFile (file))
{
// point at the existing ProjectItem for this file
setToProjectFileReference (existingItem->getID());
}
else
{
// no such object in the project, so create one..
projectItem = project->createNewItem (file, ProjectItem::waveItemType(),
file.getFileNameWithoutExtension(),
{}, ProjectItem::Category::imported,
false);
if (projectItem != nullptr)
setToProjectFileReference (projectItem->getID());
}
}
}
else if (project != nullptr)
{
// if we haven't got a legit ProjectItem, create one..
projectItem = project->createNewItem (file, ProjectItem::waveItemType(),
file.getFileNameWithoutExtension(),
{},
ProjectItem::Category::imported,
false);
if (projectItem != nullptr)
setToProjectFileReference (projectItem->getID());
}
}
else
{
if (project != nullptr)
if (auto existingProjectItem = project->getProjectItemForFile (file))
setToProjectFileReference (existingProjectItem->getID());
}
if (getFile() != oldFile)
edit.restartPlayback();
void SourceFileReference::setToProjectFileReference (ProjectItemID newID)
{
auto oldFile = getFile();
source = newID.toString();
if (getFile() != oldFile)
edit.restartPlayback();
}
source = newID.toString(); writes to a cached value connected to an undomanager
@dave96 I find myself with all the steps.
I think I understand my problem, as long as the program ( and thus the edit ) is open, if a ref file is changed the related ProjectItem is still valid. When I close and reopen the program the ProjectItem is no longer valid and I guess there is nothing I can do about it, right?
I think you misunderstood my comment. I was saying that I do think it makes sense to have it undoable.
Sorry, I misunderstand it.
However, I think it is “wrong” to make it undoable since it is not a real edit operation, like changing the start to a clip or things like that. Is there any way to not make it undoable?
Do you know why the ProjectItem isn’t loading?
I could be wrong but I think it should load ok, even if the source file can’t be found.
The thing is it kind of is. If you change the source file of a clip, you probably want to be able to change it back with an undo. It’s only this scenario that you wouldn’t want it part of the undo.
Maybe we could add an argument that bypasses the CachedValue property… I’m always a bit worried about mixing undoable and non-undoable actions for the same properties though. It’s a bit difficult to reason about what might happen.
Do you know why the ProjectItem isn’t loading?
I could be wrong but I think it should load ok, even if the source file can’t be found.
When I close and reopen the edit, the ProjectItem is not found as the call to getProjectItemAt(int idx) is passed -1.
Here is the stack trace:
The thing is it kind of is. If you change the source file of a clip, you probably want to be able to change it back with an undo. It’s only this scenario that you wouldn’t want it part of the undo.
Maybe we could add an argument that bypasses the CachedValue property… I’m always a bit worried about mixing undoable and non-undoable actions for the same properties though. It’s a bit difficult to reason about what might happen.
I agree that mixing the two contexts can be negative.
Is there, however, something I could do to get around this ?
Also I would like to add all the audio files used in the “imported” project folder, how can I do it ? does it have to be done manually ?
Are you saying that the ProjectItem isn’t being reloaded though? Or that it’s being removed somewhere?
Can you step through Project::loadProjectItem (ObjectInfo& o) to see if the ProjectItem is actually getting created? And if not, why?
Without seeing all your code its difficult to tell if you haven’t removed the ProjectItem from the Project somewhere else.
Ideally, I’d like a unit test to replicate the behaviour.
Not that I can think of at the moment… not without changes to the engine. The only hack I can think of is directly modifying the source property of the state yourself. But that could cause other problems.
Where are they now? (This is one of the problem with using the Project class, it’s very old and isn’t the way we’d do it now. It’s really just there for Waveform compatibility).
Don’t imported files go in the “Imported” directory? I.e:
ProjectItem::Category::imported: dir = dir.getChildFile (TRANS("Imported")); break;
This method is not called at all because the index passed to the method “ProjectItem::Ptr Project::getProjectItemAt (int i)” is -1.
That index is computed in the method :
int Project::getIndexOf (ProjectItemID mo) const.
{
const juce::ScopedLock sl (objectLock);
if (mo.getProjectID() == getProjectID())
{
auto itemID = mo.getItemID();
for (int i = objects.size(); --i >= 0;)
if (objects.getReference(i).itemID == itemID)
return i;
}
return -1;
}
Where are they now? (This is one of the problem with using the Project class, it’s very old and isn’t the way we’d do it now. It’s really just there for Waveform compatibility).
They are in various folders on my file system.
To import and create an audio clip I use this method :
Ok, sorry, the import stuff is something we do at the Waveform app level (it’s difficult to remember where the line is sometimes). Basically, you should check the paths of the files when you’re about to add them as ask the user (or not) if they want to copy the files in to the project folder. You can then do that and add the new clips pointing to the copies.
You can get the default dir with Project::getDirectoryForMedia (ProjectItem::Category::imported)
Right, but how does it get in to this situation?
Is Project::load() failing to actually read the item in the first place? Is o.itemID invalid?
I think I really need a small repo case that exhibits the problem to look in to this. It’s too difficult to follow what might be happening other wise.
Can you write a function that does the following:
Creates a new Project
Adds an audio file to the Project (just some test noise will do)
Creates an Edit
Opens the Edit and adds the audio file from the Project
Closes the Edit and the Project (after saving)
Deletes the audio file
Opens the Project again
Shows that there is only one ProjectItem in the Project (which will be the Edit)
That should be pretty close to what you’re doing in your app and should indicate if and where any problems are.