user_presets.h
The user's own presets: saved, named and browsed, in a folder.
#include <sonore/user_presets.h>- 4types
- 15other declarations
- Guides: Getting started, Parameters and state, Presets
On this page
Design notes
The gap
A plugin had factory presets compiled in (presets.h) and a session state every host saves (the SNRS blob, clap_wrapper.h). What it did not have was the thing a user means by "preset": a sound THEY made, saved under a name, there again tomorrow in a different project -- and in a different DAW, because the host's own preset system is the host's. A preset saved in Live's browser does not exist in REAPER, and a preset saved in a session exists only in that session.
So a user preset is a FILE, one per preset, in one folder per plugin, where every host this plugin is ever loaded into looks for it -- because it is the plugin looking, not the host.
What is in the file
The plugin's state, as the bytes the host's save path produces. Not a list of parameter values: a preset of a sampler that did not carry the sample it was made with would be a preset of a different sound. The SAME bytes, because that makes a preset exactly a session's plugin state, restored by exactly the code that restores a session -- a parameter added in a later version reads as its default from an old preset, as it does from an old session (loadStateBody's rule), and nothing about presets has a second loader to get that wrong in.
Around it, a small versioned container ("SNRP"), little-endian:
"SNRP" | u16 version | u16 oldest reader | u32 metadata bytes | u64 state bytes
metadata: a StateBag -- name, author, comment, category, tags, plugin id,
plugin version, SDK version, created, modified (Unix seconds)
state: the plugin's state blob ("SNRS...")
u32 CRC-32 of everything before it
The metadata is a StateBag for the reason state_bag.h gives: a key nobody knows is skipped, so a later version can add a field without the file becoming unreadable here. A change older readers genuinely cannot follow raises the "oldest reader" number, and this reader refuses such a file by name rather than misreading it. The checksum is what tells a file damaged in a sync or a copy from one that merely parses.
Where the folder is
Windows Documents\<Vendor>\<Plugin>\Presets, from SHGetKnownFolderPath:
the Documents a user has moved to another drive or to OneDrive
is the one they look in.
macOS ~/Library/Audio/Presets/<Vendor>/<Plugin> -- where Apple keeps a
unit's .aupreset files and Steinberg a plugin's .vstpreset files
on a Mac, so the plugin's three kinds of preset share a folder.
~/Music/Audio Music Apps is Logic's own (patches, channel
strips) and not a plugin's. ~/Library is hidden in the Finder,
which is what "Show folder" in the preset menu is for.
Linux $XDG_DATA_HOME/<vendor>/<plugin>/presets, ~/.local/share when
unset: user data that is not configuration.
SONORE_PRESET_ROOT, when set, replaces the OS's location on every
platform (<root>/<Vendor>/<Plugin>/Presets) -- for tests, validators and
portable installs -- and a plugin can name any folder it likes.
Names
What the user typed is the preset's NAME and lives in the file; the file is called something every filesystem accepts. "Bass: sub / 808?" is a fine name and no file can be called it on Windows, so the file name is the name with < > : " / \ | ? * and control characters replaced, leading dots and trailing dots and spaces taken off (Windows drops the trailing ones itself, and a file whose name changed on the way in cannot be found again), the reserved device names (CON, NUL, COM1, LPT9, COM¹...) made ordinary, and the length capped so the full path stays inside every system's limit. Two names that land on one file name get " (2)", " (3)" -- and the check is the filesystem's own, so "Bass" and "bass" are one file on Windows and macOS and two on Linux, as those systems say. Everything is UTF-8 (path_io.h). Unicode normalisation is NOT done: macOS stores names decomposed and hands them back that way, and the name shown is the one in the file anyway.
Writing
Through a temporary and a rename (user_settings.h's writeFileAtomically): a crash mid-save leaves the old preset, never half of the new one. Deleting goes to the Recycle Bin / Trash where the platform has one for that drive, and says so when it cannot -- a preset deleted for good by a misclick is the version of this feature people write angry forum posts about.
Threads
MAIN THREAD, all of it: it reads and writes files. A preset reaches the audio thread the way a host's restore does -- through the wrapper's state path, which hands the parameters over atomically (clap_wrapper.h).
What a host sees
The folder is declared to CLAP hosts through preset discovery (clap_wrapper.h), with the factory presets written out as files beside it: see syncFactoryPresetFiles for why they are files there and not only in the binary.
Types 4
Everything a preset file says about itself besides the state. Text is UTF-8; times are Unix seconds, 0 for unknown.
One preset file, decoded: what it says, and the state it carries.
One plugin's folder of user presets: listing it, and every operation on it.
Holds the list as last read; rescan() reads the folder again and says whether anything changed, which is how a preset copied in from outside -- by a file manager, a sync client, a friend's zip -- appears without the plugin being reopened. Unchanged files are not read twice: a file is known by its path, size and time, and only a new or changed one is opened.
Every operation that changes the folder rescans before returning, so the list is never older than the last thing done to it. MAIN THREAD.
A plugin instance's presets: the factory list and the user folder as one list, the preset the user is on, whether they have changed it since, and every operation a preset menu offers.
The reference has no single class for this (plugins written against it each build their own "preset manager" over its program calls and its state); this is that class, done once.
It does not know what a plugin's state IS. The wrapper hands it four functions (Hooks): the state as the host's save path writes it, a state to restore through the host's own restore path, a factory preset as a state, and a hash of the current sound for the "modified" marker. Which is what makes a preset load land exactly where a host's session restore lands, in every format -- and what lets a test drive this with four lambdas.
MAIN THREAD.
Functions 15
- fn
inline const char* userPresetExtension()The extension every user preset file carries, with its dot.user_presets.h:685 - fn
inline std::string cleanPresetName(const std::string& name)A preset name as it may be stored and shown: valid UTF-8, one line, no control characters, no spaces at the ends, at most 200 bytes. Empty means there was no name in it.user_presets.h:690 - fn
inline std::string presetFileStem(const std::string& name)The file name (without extension) a preset calleduser_presets.h:699nameis saved under: accepted by Windows, macOS and Linux alike. See the header for every rule; the result is never empty and never a device name. - fn
inline std::vector<uint8_t> encodePresetFile(const PresetMetadata& m, const std::vector<uint8_t>& state)The container, with its checksum. The state is stored as given.user_presets.h:742 - fn
inline bool decodePresetFile(const uint8_t* data, size_t size, PresetFileContents* out, std::string* why = nullptr)Read a preset file's bytes back. Refuses -- leavinguser_presets.h:859outempty and saying why -- anything that is not a whole, undamaged preset holding a Sonore plugin state: a preset applied half-way is worse than one not applied. Never trusts a size from the file: every length is checked against what is actually there before it is used. - fn
inline bool readPresetFile(const std::string& path, PresetFileContents* out, std::string* why = nullptr)Read the preset file atuser_presets.h:889path(UTF-8) and decode it with decodePresetFile's checks, checksum included. A file larger than any preset (256 MB) is refused before it is read in full. False with the reason inwhywhen the file cannot be read or is not a whole preset;outis meaningful only on true. Main thread: it reads the file. - fn
inline bool readPresetMetadata(const std::string& path, PresetMetadata* out, std::string* why = nullptr)The metadata alone, for listing a folder: the header, the metadata and the first bytes of the state, never the rest. Everything a list shows is checked; the checksum, which needs the whole file, is checked when the preset is loaded.user_presets.h:901 - fn
inline bool writePresetFile(const std::string& path, const PresetMetadata& meta, const std::vector<uint8_t>& state, std::string* why = nullptr)Write a preset file through a temporary and a rename.user_presets.h:938 - fn
inline std::string defaultUserPresetFolder(const std::string& vendor, const std::string& plugin)user_presets.h:947<root>/<Vendor>/<Plugin>/Presetswhere SONORE_PRESET_ROOT is set, else the OS's place for them (see the header). Empty when this machine has none. - fn
inline std::string defaultFactoryPresetFolder(const std::string& vendor, const std::string& plugin)Where syncFactoryPresetFiles keeps the factory presets as files: the OS's cache location, because the binary can always write them again.user_presets.h:965 - fn
inline bool moveFileToTrash(const std::string& path, std::string* why = nullptr)Move a file to the Recycle Bin (Windows), the Trash (macOS) or the freedesktop.org trash (Linux). False, with the reason, when it could not -- and then the file is still there: this never falls back to deleting for good, which is a decision for the person who asked.user_presets.h:985 - fn
inline bool showFolderInFileManager(const std::string& folder)Open a folder in Explorer, the Finder or the desktop's file manager. Never waits for it: the call returns as soon as the request is made, because a file manager that takes a second to start is a second the host's interface would otherwise be frozen for.user_presets.h:1049 - fn
inline std::string vstPresetClassIdFor(const char* pluginId)The class id a .vstpreset for this plugin carries, as 32 hex characters: the id's bytes in memory order, which is how a Mac or Linux host prints it and how Steinberg's convention prints it everywhere.user_presets.h:1088 - fn
inline bool vstPresetMatchesPlugin(const std::string& classIdText, const char* pluginId)Whether a .vstpreset's class id is this plugin's.user_presets.h:1104 - fn
inline bool syncFactoryPresetFiles(const std::string& folder, const std::vector<std::string>& names, const PresetMetadata& common, const std::function<bool(int, std::vector<uint8_t>&)>& stateFor, std::vector<std::string>* paths = nullptr, std::string* why = nullptr)The plugin's factory presets, written out as preset files inuser_presets.h:1133folder-- one per preset, named "01 Warm Glue.sonorepreset" and so on -- and any other preset file there removed. A file already holding exactly the right bytes is left alone, so doing this at every host scan writes nothing after the first.