# SourceFileReference

**URL:** <https://forum.juce.com/t/sourcefilereference/62334>\
**Category:** Tracktion Engine\
**Created:** [July 30, 2024, 9:19am UTC](https://forum.juce.com/t/sourcefilereference/62334 "2024-07-30T09:19:05Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![Fernando](https://avatars.discourse-cdn.com/v4/letter/f/779978/32.png) [@Fernando](https://forum.juce.com/u/Fernando)\
**Post date:** [July 30, 2024, 9:19am UTC](https://forum.juce.com/t/sourcefilereference/62334/1 "2024-07-30T09:19:05Z")

</div>

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 :

1. 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”;
2. 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.

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [July 30, 2024, 10:37am UTC](https://forum.juce.com/t/sourcefilereference/62334/2 "2024-07-30T10:37:28Z")

</div>

Are you using a `te::Project`?  
If not you probably want to use `setToDirectFileReference`.

> [@Fernando](#):
>
> 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”;

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 `Project`s 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.

> [@Fernando](#):
>
> 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.

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.

---

<div class="post-metadata">

**Author:** ![Fernando](https://avatars.discourse-cdn.com/v4/letter/f/779978/32.png) [@Fernando](https://forum.juce.com/u/Fernando)\
**Post date:** [July 30, 2024, 12:20pm UTC](https://forum.juce.com/t/sourcefilereference/62334/3 "2024-07-30T12:20:10Z")

</div>

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.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/1/9/198fc906cc08bce4b2f70630de38c408043f5200.png)

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

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [July 30, 2024, 12:50pm UTC](https://forum.juce.com/t/sourcefilereference/62334/4 "2024-07-30T12:50:58Z")

</div>

> [@Fernando](#):
>
> Exactly, it doesn’t make sense , but if we look at the setToProjectFileReference function :

I think you misunderstood my comment. I was saying that _I do think it makes sense_ to have it undoable.

* * *

> [@Fernando](#):
>
> The ProjectItem associated with ProjectItemID is null so I don’t know how I can get its name as you can see.

Why is this the case? Perhaps I’ve misunderstood the steps to get to this state?  
What I thought was happening is:

- You have a `Project` with a `ProjectItem` that is a wav file
- In that `Project` you have an `Edit`
- In that `Edit` you have a `WaveAudioClip` that has the above’s `ProjectItemID` as it’s `source` property
- The wav file is deleted
- But the `ProjectItem` should still be valid in the `Project`

At what point is the (now empty) `ProjectItem` failing to load?

---

<div class="post-metadata">

**Author:** ![Fernando](https://avatars.discourse-cdn.com/v4/letter/f/779978/32.png) [@Fernando](https://forum.juce.com/u/Fernando)\
**Post date:** [July 30, 2024, 1:45pm UTC](https://forum.juce.com/t/sourcefilereference/62334/5 "2024-07-30T13:45:25Z")

</div>

@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?

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [July 30, 2024, 1:53pm UTC](https://forum.juce.com/t/sourcefilereference/62334/6 "2024-07-30T13:53:45Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Fernando](https://avatars.discourse-cdn.com/v4/letter/f/779978/32.png) [@Fernando](https://forum.juce.com/u/Fernando)\
**Post date:** [July 30, 2024, 2:55pm UTC](https://forum.juce.com/t/sourcefilereference/62334/7 "2024-07-30T14:55:26Z")

</div>

@dave96

> 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:

![image](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/b/6/b6b18e4f314f5fc48e092af0fe0f0ef7644b66c4.png)

![image](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/3/d/3ddb9160490f9e01c9755bf3c7788d2076166068.png)

![image](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/2/2/22ce589e4b30dc980cf917a511a02582e02abd67.png)

![image](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/8/6/86dc177fc757c20378f96f2a593b191e81bb3d85.png)

> 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 ?

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [July 30, 2024, 3:58pm UTC](https://forum.juce.com/t/sourcefilereference/62334/8 "2024-07-30T15:58:43Z")

</div>

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.

* * *

> [@Fernando](#):
>
> Is there, however, something I could do to get around this ?

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.

* * *

> [@Fernando](#):
>
> 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 ?

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:

```auto
ProjectItem::Category::imported: dir = dir.getChildFile (TRANS("Imported")); break;

```

---

<div class="post-metadata">

**Author:** ![Fernando](https://avatars.discourse-cdn.com/v4/letter/f/779978/32.png) [@Fernando](https://forum.juce.com/u/Fernando)\
**Post date:** [July 30, 2024, 11:54pm UTC](https://forum.juce.com/t/sourcefilereference/62334/9 "2024-07-30T23:54:29Z")

</div>

@dave96

> [@dave96](#):
>
> Can you step through `Project::loadProjectItem (ObjectInfo& o)` to see if the `ProjectItem` is actually getting created? And if not, why?

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 :

```auto
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 :

```auto
WaveAudioClip::Ptr newClip = audioTrack->insertWaveClip(file.getFileNameWithoutExtension(), file,position, false);

WaveAudioClip::Ptr ClipTrack::insertWaveClip (const juce::String& name, const juce::File& sourceFile,
                                              ClipPosition position, bool deleteExistingClips)
{
    return engine::insertWaveClip (*this, name, sourceFile, position, deleteExistingClips ? deleteExistingClips::yes : deleteExistingClips::no);
}

```

Audio files are not copied under the “imported” folder.

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [July 31, 2024, 12:10pm UTC](https://forum.juce.com/t/sourcefilereference/62334/10 "2024-07-31T12:10:38Z")

</div>

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

* * *

> [@Fernando](#):
>
> 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 :

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.
