Search guides and the APICtrl K
  1. API reference
  2. Plugin model

user_presets.h

The user's own presets: saved, named and browsed, in a folder.

#include <sonore/user_presets.h>
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

↑↓ to move↵ to openEvery public name in the headers, and every guide