Skip to content

Persistence

Song.marbble/
project.json the Project (the active alternative), pretty-printed, with plug-in states (base64)
project.autosave.json only while there are unsaved changes (see below)
project.json.bak the previous save, only with Settings › General › Backups at 0
Audio Files/ recordings, conversions, joins, reverses, bounce-in-place results
Freeze Files/ freeze renders ("<track> Freeze.wav")
Alternatives/<name>/project.json the inactive project alternatives
Backups/<alternative>/<time>.json earlier versions of each alternative (File › Revert to)
  • ClipRef::path is relative to the folder, except for files referenced in place (an imported WAV already at the project rate stays where it is, with an absolute path).
  • Save As to a new folder copies every clip inside the old folder (or with a relative path) into the new Audio Files/.
  • Plug-in state is "<component base64>:<controller base64>" in PluginSlot::state. It is filled only when saving, autosaving, checkpointing with plugins, or rebuilding; the UI never sees it (for_ui).
  • Recordings go to Audio Files/ named <track>#NN.wav (or <project> - <track>#NN.wav), unless Settings › Recording names another folder, in which case they go to <folder>/<project name>/.
  • Opening a project whose sample rate differs from the device switches the device to the project’s rate, as Logic does.

session/alternatives.rs. The active alternative is always the top-level project.json, its name in Project::alternative (set to the project’s name at the first save), so autosave, recovery, backups and older builds all treat it as the project and need no changes. Inactive alternatives are Alternatives/<name>/project.json; they reference clips by the same relative paths, so they share Audio Files/.

  • New Alternative saves, copies project.json into Alternatives/<current>/, and renames the open project (on disk too) to the new name.
  • Switch reads the target first (a damaged one changes nothing), saves the active one, copies project.json into its folder, writes the target as project.json, removes the target’s folder and reopens. Each write is atomic; a crash between steps leaves at worst a duplicate (alternatives() lists a name once), never a lost alternative.
  • Rename of the active one rewrites the name in project.json and the autosave at once, unsaved changes or not; of an inactive one, renames its folder. Backups move with the name.
  • Delete removes the folder and the backups; deleting the active one writes another into project.json without saving. A project keeps at least one.
  • Names are folder names: check_name refuses empty names, a leading dot, / \ : * ? " < > | and control characters. The UI mirrors it (alternativesModel.ts).
  • Save As writes only the active alternative into the new folder.

session/backups.rs. Session::write_project is what save writes through: with Prefs.general.backups > 0, the project.json being replaced (if readable and different) goes to Backups/<its alternative>/<time>.json, where <time> is that file’s modification time in UTC (2026-10-29T143205.123Z, sortable, valid on every file system; a clash adds a millisecond), then all but the newest N are deleted, and any project.json.bak goes (it is superseded). With 0, the single .bak is kept as before (recovery::write_project_file). Opening falls back from project.json to the autosave, the .bak, then the newest backup of the active alternative (latest_backup: backups of folders in Alternatives/ are skipped). revert_to_backup saves, then writes the chosen version through write_project, so the state before the revert becomes a backup; revert_to_saved discards the autosave and reopens.

The native File › Project Alternatives and File › Revert to submenus are rebuilt by the set_project_menus command (menu.rs) from the list the frontend reads with project_files and labels in local time (src/projectFiles.ts, run whenever the path, saved and dirty flags or the alternative change). Their ids are alternative:<name> and revert:<file>.

  • Session::autosave_job is polled from the idle timer. When the interval in Settings › General has passed, the project is dirty and nothing is recording, it serializes the project (with plug-in states, so on the main thread) and returns a job; main.rs writes it on a new thread to project.autosave.json via a temporary file and rename. A hash skips writing an unchanged document.
  • Saving deletes the autosave (in the new and the old folder); so does Don’t Save.
  • recovery_info(dir) reports an autosave newer than project.json. recoverable_projects checks the recent list plus every .marbble folder in the projects folder and in ~/Music/Marbble. Untitled projects autosave into their (not yet saved) folder in the projects folder, which is how they are found.
  • open_recovered loads the autosave and marks the project dirty, so saving writes it back.

The config folder is $XDG_CONFIG_HOME/marbble, else %APPDATA%\marbble, else ~/.config/marbble (plugin_db::config_dir; macOS uses ~/.config/marbble too).

Marbble was called Fermata up to 0.1.0-alpha.5, and old_name.rs is the one place that knows it: .fermata project folders still open and save in place, and old_name::migrate, first thing at launch, moves the old config folder, ~/Music/Fermata Instruments and the webview’s storage (named after the old identifier studio.fermata.daw) to their new names, never over anything already there. src/oldName.ts then renames the fermata. localStorage keys. An existing ~/Music/Fermata stays the default projects folder until ~/Music/Marbble exists, since recent projects point into it.

File Written by Contents
settings.json src-tauri/src/settings.rs Sections audio (AudioConfig), prefs (daw_engine::prefs::Prefs: general, recording, midi, editing, latency, click defaults), ui (UiPrefs: startup, confirm_unsaved, snap, nudge, keymap overrides, theme, ui_scale, wave_style, click_locates) and plugins (disabled ids, extra folders). Every field defaults, and a malformed section is replaced by defaults without losing the others. Written atomically.
recent.json src-tauri/src/recent.rs Up to 20 canonical project paths, newest first (not written atomically).
plugins.json plugin_db.rs The plug-in scan cache.
plugin-scan.json plugin_db.rs The last scan’s report (bundles, plug-ins, failures with reasons), for Settings › Plug-ins.
presets/<effect>/<name>.json presets.rs User presets of the built-in effects and the Channel EQ (see Presets).
channel-strips/<name>.json strip_settings.rs Saved channel strip settings (see Channel strip settings).
controller-assignments.json session/controllers.rs MIDI Learn’s controller assignments: controller (port without ALSA’s client:port numbers, channel, CC number or pitch bend), target (track id and parameter, plug-in instance id with its plug-in id, Channel EQ setting, Stereo Out or transport), mode and range. Global, like Logic’s; written atomically.
templates/<name>.json session/templates.rs User project templates (File › Save as Template…): a Project without regions, clips, freeze state or alternatives, with plug-in states. new_from_template loads one as a new untitled project at the device’s rate, keeping its ids.

Interface-only state lives in the webview’s localStorage:

Key Module Contents
marbble.theme themes.ts Theme id (a copy of ui.theme, for the first paint).
marbble.uiScale uiScale.ts Interface scale, 0.9 to 2 (a copy of ui.ui_scale).
marbble.waveStyle waveform.ts Waveform drawing style (a copy of ui.wave_style).
marbble.paneSizes components/Splitter.tsx Library, Inspector, Browser, Mixer, Editor and header sizes.
marbble.trackHeights:<project path> layout.ts { base, heights, output } for one project (output: the Output track shown).
marbble.bounce components/Bounce.tsx Tail, format, resolution, sample rate and normalize of the bounce and export dialogs.
marbble.bounce-in-place components/Bounce.tsx Bounce in Place options.
marbble.pianoRoll components/pianoRollEditor/prefs.ts Piano Roll view and Time Quantize settings.
marbble.eventList components/EventList.tsx Event List filters, the type + adds, Catch and MIDI Out.
marbble.stripSilence components/StripSilence.tsx The Strip Silence sheet’s last settings.
marbble.showOverview components/Overview.tsx Whether the overview strip replaces the scroll bar.
marbble.fxPanelSizes fxDockState.ts The last size of each effect type’s floating panel.
marbble.detachedPanels detach.ts Which panels were detached at quit, and each kind’s window geometry.

Appearance settings (theme, size, waveforms) are saved in settings.json by appearance.ts; the localStorage copies only let the window open in the right theme and size before the settings have loaded.