Presets: file creation, loading, saving, editing

Hi friends,

This is sort of a follow-up to this earlier post – I’ve decided to implement my own onboard preset browser/loading & saving capabilities within my plugin. Doing file management in Juce is completely new to me, so I apologize if these are basic questions, but I want to make sure I’m not making any silly mistakes.

First, I tried my hand at creating a function that would return the path to where preset files should be loaded from & saved to. I’m hoping this will work on Mac, Windows, or Linux…

File ImogenAudioProcessor::getPresetsFolder()
{
	File rootFolder = File::getSpecialLocation(File::SpecialLocationType::userApplicationDataDirectory);
	
#ifdef JUCE_MAC
	rootFolder = rootFolder.getChildFile("Audio").getChildFile("Presets");
#endif
	
	rootFolder = rootFolder.getChildFile("Ben Vining Music Software").getChildFile("Imogen"); // "Imogen" is the name of my plugin
	Result res = rootFolder.createDirectory(); // creates if not existing
	
	return rootFolder;
};

Are there any glaring issues with this? Do I need to add any conditional code to get this to work for Windows or Linux?

Here are my functions to save, load and delete preset files. I don’t have a specific question here, but I would greatly appreciate it if anyone could point out any glaring errors or stupid mistakes…

void ImogenAudioProcessor::savePreset(String presetName)
{
	// this should work for saving new presets or altering existing ones, I think...
	
	File writingTo = getPresetsFolder().getChildFile(presetName);
	
	auto state = tree.copyState();
	std::unique_ptr<juce::XmlElement> xml (state.createXml());
	
	bool existed = writingTo.existsAsFile();
	
	xml->writeTo(writingTo); // if the file already existed, does it need to be cleared first?
	
	if(! existed)
		writingTo.setCreationTime(Time::getCurrentTime());
	
	writingTo.setLastModificationTime(Time::getCurrentTime());
	
};
void ImogenAudioProcessor::loadPreset(String presetName)
{
	File presetToLoad = getPresetsFolder().getChildFile(presetName);
	
	if(presetToLoad.existsAsFile())
	{
		setStateInformation(&presetToLoad, int(presetToLoad.getSize()));
		presetToLoad.setLastAccessTime(Time::getCurrentTime());
	}
};
void ImogenAudioProcessor::deletePreset(String presetName)
{
	File presetToDelete = getPresetsFolder().getChildFile(presetName);
	
	if(presetToDelete.existsAsFile())
		presetToDelete.moveToTrash(); // or should I use .deleteFile() ?
};

Thanks everyone! :grinning:

1 Like

You don’t seem to be doing anything with res. What I do is giving rootFolder an initial value of juce::File::getSpecialLocation (juce::File::userDocumentsDirectory), then

auto folder{ rootFolder.getChildFile ("etc etc") };
if (folder.createDirectory().wasOk())
    rootFolder = folder;

No, you can overwrite, though you may want to ask for confirmation. I don’t know why you’d change the timestamps after writing/reading, which already does it.

This won’t work. setStateInformation expects void*, not File*. File is not a chunk of binary data. You need something like

// assuming tree is APVTS
auto xml{ juce::XmlDocument::parse (presetToLoad) };
tree.replaceState (juce::ValueTree::fromXml (*xml));
2 Likes

Thank you for the detailed answer!

You’re right that I don’t actually need to do anything with res, I just wanted a way to automatically create the presets folder if it doesn’t already exist (ie, if it’s the first time the plugin is launched, etc). How about replacing the res line with:

if(! rootFolder.isDirectory())
		rootFolder.createDirectory();

Do I want to be in userDocumentsDirectory instead of userApplicationDataDirectory?

I wasn’t sure if the write operation would automatically update the timestamp, so thanks for clarifying that!

And thanks for clarifying the setStateInformation() call, those 2 lines you provided work nicely! This is a more tangential C++ question, but I was under the impression that a void* could point to any datatype, which is why I thought that might work…

Thanks again, this is quite helpful!

If rootFolder is not a folder, it’s because it’s a file, so createDirectory would fail, or it doesn’t exist, so createDirectory would succeed. If you want to make a check, use the Result of createDirectory.

That depends on how “far from the user” you want this location to be. In Windows, userDocuments would be My Documents, and userAppData would be userprofile\appdata\roaming.

You can cast any pointer to void*, the point is what for. A void* cannot be dereferenced until it’s converted to something else. C uses void* to represent a pure memory address, of which you know nothing else -for things like copying chunks of memory that may contain anything. If you convert File* to void*, it’s because you want to treat the File object as a chunk of memory. But a File object doesn’t hold the actual content of the file -it holds a String with its path. If you want to read a file, you have to create a stream to place its content. In this case, XmlDocument::parse does it for you.

2 Likes

Ah, I see! So if my goal is just to create the directory if it doesn’t exist, should I do this:

if(! rootFolder.isDirectory() && ! rootFolder.existsAsFile())
		rootFolder.createDirectory(); 

I don’t think I really need to check the result of createDirectory()… unless I should be having this getPresetsFolder() function return a pointer & return nullptr if createDirectory() fails? I just want to ensure that the preset folder I’m working with exists ¯_(ツ)_/¯

Interesting - so is it basically up to me where to put my presets folder? I’m on a Mac, and with the function the way I currently have it, it takes me to Library/Audio/Presets/"Ben Vining Music Software"/"my plugin name", which if I understand correctly is a fairly standardized location for preset files…? Is there a “standard” place for presets on Windows, or does it really not matter?

Ohhhhhh I understand! Thank you for the explanation!

Thanks again for all your help, and merry Christmas & happy holidays :slightly_smiling_face:

Those are the checks I meant -you don’t need to make them. Just call createDirectory. If there’s a file with that name or for some reason the folder couldn’t be created, it will return a fail Result, otherwise it will succeed.

I’m not sure about Mac. On Windows, most apps place their presets in Documents, but some use AppData. I prefer Documents because if the user wants to take their presets somewhere else, they’re easier to find.

Merry Christmas to you too :blush:

1 Like

So in my getPresetsFolder() function, should I just always be calling createDirectory()? I was worried that if the presets folder did already exist, it might create duplicates or do something else funky…

Awesome, so I’ll use that path for windows :slightly_smiling_face:
On Windows, would the typical path be Documents\CompanyName\AppName, or something else?

Thanks again! Merry Christmas!

No, that won’t happen. You may of course call createDirectory just once, and save the preset folder’s File object as a member variable.

Yup, typically.

2 Likes

Thank you :slightly_smiling_face: