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::deletePreset(String presetName)
{
File presetToDelete = getPresetsFolder().getChildFile(presetName);
if(presetToDelete.existsAsFile())
presetToDelete.moveToTrash(); // or should I use .deleteFile() ?
};
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));
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:
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…
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.
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
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.
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
On Windows, would the typical path be Documents\CompanyName\AppName, or something else?