Repository navigation
Theme Format
This page describes the files that make up a Sonorium theme, so you can build one by hand, from an empty folder, and add it to Sonorium. It matches Sonorium 1.7.1.
The full technical specification, with the exact mixer maths, is docs/THEME_FORMAT.md in the repository.
A theme is a folder. The audio files in it are the sounds; two JSON files say how each sound plays. Think of it as a mixing desk packed in a box: the audio files are the instruments, metadata.json is the desk's settings, and presets.json holds saved settings you can switch between.
Thunderstorm/ <- the theme folder
metadata.json <- theme info, track settings, group settings
presets.json <- presets and sequences
Rain.wav <- a track
Gusts.wav <- a track
Wind/ <- a group
Wind 1.wav <- a track in the group
Wind 2.wav
ATTRIBUTION.md <- optional: credits, for people
MANIFEST.json <- optional: source and licence, for tools
| File | Required | What it is |
|---|---|---|
| Audio files | Yes, at least one | The sounds. A folder with no audio file is not listed as a theme. |
metadata.json |
No, but write it | Name, description, icon and how every track and group plays. Without it Sonorium makes one with defaults. |
presets.json |
No | Presets and sequences. |
MANIFEST.json |
No | Source and licence details. Sonorium's mixer doesn't read it. |
ATTRIBUTION.md |
No | Credits for the sounds, in plain text. Sonorium's mixer doesn't read it. |
All JSON files are UTF-8 text. Write non-English characters, emoji included, as \uXXXX escapes ("🔥" for 🔥): every install reads those correctly.
Every theme is one folder inside Sonorium's audio folder.
| Install | Audio folder |
|---|---|
| Home Assistant app |
/media/sonorium (the media share, folder sonorium) |
| Docker |
/media/sonorium inside the container: the host folder you mapped to it (./sonorium/media in the Getting Started compose file) |
| Windows app | The themes folder next to Sonorium.exe
|
| Rule | Detail |
|---|---|
| Formats |
.mp3, .wav, .flac, .ogg (upper or lower case) |
| Best format | 44.1 kHz mono. Sonorium mixes in 44.1 kHz mono: other rates are resampled and stereo is mixed down to mono as the file is read. |
| Where | At the top of the theme folder, or in a group folder one level down. Files deeper than that are not found. |
| Ignored | Files and folders whose name starts with . or _ (useful for drafts: _drafts/). |
| Order | Top-level files sorted by name, then each group (sorted by name) with its files sorted by name. |
Each track is known by its key: its path inside the theme without the extension, with / between group and file.
| File | Key |
|---|---|
Rain.wav |
Rain |
Wind/Wind 1.wav |
Wind/Wind 1 |
Keys are what metadata.json and presets.json use to name tracks. The web UI shows only the part after the /. Two files with the same name and different extensions in one folder (Rain.wav and Rain.mp3) get the same key: give each file a different name.
-
Theme folder: any name not starting with
.. It is the theme's name whenmetadata.jsongives none. -
Group folder: at most 60 characters; not
/ \ : * ? " < > |; not starting with.or_; not ending with.or a space. The folder name is the group's name.
{
"spec_version": 2,
"id": "c3a9e1f0-5d2b-4f6a-8e47-1b9d0c7a2e58",
"name": "Thunderstorm",
"description": "Rain and wind, with thunder rolling in.",
"icon": "⛈️",
"categories": ["Weather"],
"ebb_duration": true,
"intrusions_retired": true,
"auto_retired": true,
"tracks": { "Rain": { "playback_mode": "continuous", "volume": 0.7 } },
"groups": {}
}| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
spec_version |
integer | Yes: write 2
|
2 |
The file layout version. Missing or below 2 marks an older theme, which Sonorium converts when it loads it (see What Sonorium does when it loads a theme). |
id |
string | Recommended | A new random UUID, saved on first load | The theme's permanent ID. Channels, playlists and linked files find the theme by it, so renaming the folder keeps them working. Make one with any UUID tool (python -c "import uuid; print(uuid.uuid4())"); every theme needs its own. Import theme gives the imported theme a new one. |
name |
string | No | The folder name | The name shown in Sonorium. |
description |
string | No | "" |
Shown under the name. |
icon |
string | No | "" |
One emoji. Empty: Sonorium picks one from the theme's name. |
is_favorite |
true/false | No | false |
Listed under Favorites. |
categories |
list of strings | No | [] |
Category names. A category that doesn't exist yet is listed once a theme uses it. |
short_file_threshold |
number, seconds | No |
15 (Settings › Advanced) |
Only used for tracks without a playback_mode (see auto below). |
tracks |
object | No | {} |
Settings per track, by key. A track without an entry uses every default. |
groups |
object | No | {} |
Settings per group, by group name. |
links |
object | No | none | Files used from other themes (see Linked files). |
attribution |
object | No | none | Source and licence (see attribution). |
ebb_duration, intrusions_retired, auto_retired
|
true/false | Yes for a new theme: write true
|
false |
Marks that the three one-time conversions for older themes are done. Without "ebb_duration": true, Sonorium reads your Ebb & Flow Duration values as the old Interval and turns them around (0.3 becomes 0.7) the first time it loads the theme. |
Each entry in tracks holds any of these fields. Leave a field out to use its default.
| Field | Type, range | Default | What it does |
|---|---|---|---|
playback_mode |
"continuous", "sparse", "presence", "unassigned"
|
"auto" |
How the track plays: see Playback modes. Always write it. |
volume |
0.0 to 1.0 | 1.0 |
The track's level (1.0 is the file as it is). For Ebb & Flow, the top of the Volume range. |
presence |
0.0 to 1.0 | 1.0 |
Interval for Intermittent, Duration for Ebb & Flow, the chance of being picked in a group (see the modes). |
muted |
true/false | false |
Leaves the track out of the mix. |
seamless_loop |
true/false | false |
Gapless: a Background track loops with a hard cut instead of a 1.5 s crossfade. Only for files cut to loop perfectly. |
fade_in, fade_out
|
0 to 30 seconds |
0 (off) |
Fade in / Fade out: the file fades in each time it starts and out each time it ends, loops included. For files that start or end abruptly. |
volume_low |
0.0 to 1.0 | 0.2 |
Ebb & Flow: the bottom of the Volume range. The level moves between volume_low and volume. |
rise |
0.0 to 1.0 | 0.0 |
Ebb & Flow Rise: how fast the level climbs, from 0 (Gradual) to 1 (Sudden). |
pause_between |
true/false | false |
Ebb & Flow Pause between plays: the file plays once from start to end, fades out, pauses, then comes back. |
pause_min, pause_max
|
seconds, 0 or more |
30, 300
|
Ebb & Flow Pause: each pause is a random time in this range. It also staggers the first start. |
exclusive |
true/false | false |
One at a time (legacy), from themes made before groups. Use a group instead. |
playback_mode |
In the web UI | What you hear |
presence means |
|---|---|---|---|
"continuous" |
Background | Plays all the time, looping smoothly. Rain, wind, a fire. | Not used. |
"sparse" |
Intermittent | Plays the whole file once, then stays quiet before playing again. Bird calls, a door. | Interval: the wait between plays. 1.0 waits about 3 minutes, 0.0 about 30 minutes, each wait 30 % shorter or longer at random. These are the defaults of Wait between plays and Wait randomness on Settings › Advanced. |
"presence" |
Ebb & Flow | Always heard, the level drifting up and down between volume_low and volume. Surf, gusting wind. |
Duration: how long the level stays at each value; higher is longer. |
"unassigned" |
Unassigned | Nothing. The track waits to be placed. | Not used. |
"auto" |
(none) | The default for a track without a mode, decided by the file's length when it starts: at presence 1.0 Background; below 1.0, files shorter than short_file_threshold play Intermittent, longer ones Ebb & Flow. Write a real mode instead. |
Depends on the mode it becomes. |
A track inside a group plays the group's way, whatever its own playback_mode. Its volume and presence still count (see below).
A subfolder with audio in it is a group. Its entry in groups (keyed by the folder name) sets how its tracks play together. A group without an entry is an Intermittent group with the defaults.
mode |
In the web UI | What you hear |
|---|---|---|
"intermittent" (or missing) |
Intermittent group, in the Intermittent section | One track at a time, never overlapping. A track plays its whole file once; then the group waits for the Gap and picks the next one. The same track never plays twice in a row, and a track that just played is less likely to be picked next. |
"merry_go_round" |
Merry-go-round, in the Background section | One continuous bed: one file plays, then crossfades into another picked at random. A group with one file crossfades into itself. |
"ebb_flow" |
Ebb & Flow group, in the Ebb & Flow section | Every file plays at once, each rising and falling between its own volume_low and volume. |
"intrusion" is a retired mode: Sonorium converts such groups when it loads the theme.
| Group field | Type, range | Default | Applies to | What it does |
|---|---|---|---|---|
mode |
see above | "intermittent" |
all | How the group plays. |
volume |
0.0 to 1.0 | 1.0 |
all | Group Volume: multiplies each track's own volume. |
presence |
0.0 to 1.0 | 1.0 |
all | Group Interval or Duration: multiplies each track's own presence. |
muted |
true/false | false |
all | Mutes every track in the group. |
gap_min, gap_max
|
seconds, 0 or more | 120 each (Settings › Advanced) | Intermittent | Gap: after a track finishes, the group waits a random time in this range. The web UI shows it in minutes. With only one of the two set, it is used for both. |
crossfade |
seconds, 1 to 60 | 10 |
Merry-go-round | Crossfade: the overlap between one file and the next. A short file gets a shorter crossfade (at most half its length). |
pause_between |
true/false | false |
Ebb & Flow | Pause between plays for the whole group: every file pauses between plays, at staggered times, so at least one is always playing. |
rise |
0.0 to 1.0 | none | Ebb & Flow | Same Rise for every file: one Rise for the whole group instead of each file's own. Leave it out to use each file's own. |
A group works like a submix fader on a mixing desk: its volume and presence scale its tracks' own values. A track at 0.5 in a group at 0.5 plays at 0.25.
In an Intermittent group or a Merry-go-round, each track's presence is its chance of being picked: with four tracks at 1.0, 1.0, 1.0 and 0.2, the 0.2 track plays about one turn in ten. In a Merry-go-round, a track at 0 (or muted) is never picked.
A linked file is an audio file of another theme, used by this theme without a copy. It is listed in links and has its own entry in tracks like any other track:
"links": {
"Crickets": { "theme": "0b6f2d4e-7a51-4c3e-9d27-5e8a1f3c9b62", "track": "Crickets" },
"Night/Owl": { "theme": "0b6f2d4e-7a51-4c3e-9d27-5e8a1f3c9b62", "track": "Owl" }
},
"tracks": {
"Crickets": { "playback_mode": "sparse", "muted": true }
}| Part | Rule |
|---|---|
| Key |
"<name>" for an ungrouped link, "<group>/<name>" for one in a group. It must not be the key of a file of this theme. |
theme |
The source theme's id (from its metadata.json). |
track |
The source track's key. Only real files can be linked, not another theme's links. |
| Missing source | The link is kept and shown as Missing; it doesn't play. |
| Export | Export copies linked files into the zip as normal files, so an exported theme works without the theme it linked from. |
Since a link points at a theme id, it only works on the install that has that theme. To share a theme with a linked file, export it from Sonorium, or copy the file in.
Optional. Shown to people who look at the theme's source. Any of:
| Field | Example |
|---|---|
source |
"Recorded by the theme author" |
source_url |
The page the sounds came from |
license |
"CC BY 4.0" |
license_url |
"https://creativecommons.org/licenses/by/4.0/" |
imported_date |
ISO 8601 time, for a tool that made the theme |
imported_by |
The tool that made the theme |
The first time Sonorium saves the theme (any change in the editor, an import, a conversion), it rewrites metadata.json with every field listed above, defaults filled in. Any other field at the top level, or inside a track entry, is dropped. Unknown fields inside a group entry and inside presets.json are kept.
{
"presets": {
"<preset id>": { "name": "...", "is_default": true, "tracks": {}, "groups": {} }
},
"sequence_defaults": { "hold_min": 300, "hold_max": 900, "ramp": 30 },
"sequences": {}
}A preset is a saved version of the theme's mix: the same storm with the thunder off, or with the rain louder. A channel playing a preset hears the theme's own settings with the preset's values on top. A field the preset leaves out follows the theme.
| Field | Type | Required | What it does |
|---|---|---|---|
| Preset ID (the key) | string | Yes | Lower-case letters, digits and _. Sonorium makes IDs from the name the same way: "Full storm" becomes full_storm. |
name |
string | Yes | Shown in preset lists. |
is_default |
true/false | No | The default preset, marked ★: picked when a channel chooses the theme. Mark one preset; if several are marked, the first in the file wins. |
tracks |
object | No | Per track key: any of volume, presence, muted, playback_mode, seamless_loop, exclusive, volume_low, pause_between, pause_min, pause_max, with the ranges in Track settings. |
groups |
object | No | Per group name: any of volume, presence, muted, gap_min, gap_max, pause_between, rise. A group's mode and crossfade belong to the theme and can't be set by a preset. |
When a channel switches preset, volumes and intervals glide to the new values over 3 seconds, and tracks the preset turns on or off fade in or out over 3 seconds. A new playback_mode or seamless_loop is heard the next time the theme starts.
A preset made in the web UI lists every track with every field, so it doesn't depend on the theme's settings. By hand, listing only what changes is enough; listing everything protects the preset from later changes to the theme.
A sequence plays some of the theme's presets in order on a channel, gliding from one to the next: drizzle, then rain, then a downpour. It is listed with the presets, marked with the sequence icon.
| Field | Type, range | Default | What it does |
|---|---|---|---|
sequence_defaults.hold_min, .hold_max
|
seconds, 0 to 604800 |
300, 900
|
The theme's Time on each preset: each preset plays for a random time in this range. |
sequence_defaults.ramp |
seconds, 0 to 604800 | 30 |
The theme's Ramp: how long the glide to the next preset lasts. |
| Sequence ID (the key) | string | Made like a preset ID. | |
name |
string, 1 to 100 characters | required | Shown with the presets. Unique in the theme. |
presets |
list of preset IDs | required | The presets, in play order. Presets not listed are never reached. |
at_end |
"reverse", "restart", "stop"
|
"reverse" |
At the end: Reverse plays the list backwards (1 2 3 2 1 2 …), Start again goes back to the first preset, Stop stays on the last one. |
hold_min, hold_max, ramp
|
seconds, or null
|
null |
The sequence's own timing. null uses sequence_defaults. hold_min must not be above hold_max. |
owner |
null |
null |
Write null. |
while |
object | none | While playing changes per step (below). |
transitions |
object | none | Coming in / Leaving styles per step (below). |
Changes how tracks play while one step plays, for this sequence only, on top of that step's preset. Keyed by preset ID:
"while": {
"full_storm": {
"tracks": { "Rain": { "volume": 0.9 } },
"groups": { "Thunder": { "gap_min": 30, "gap_max": 90 } }
}
}| Part | Fields |
|---|---|
tracks |
Per track key: any of volume, volume_low, presence, muted, pause_between, pause_min, pause_max. |
groups |
Per group name: any of volume, presence, muted, gap_min, gap_max, pause_between, rise. |
A field left out (or null) keeps the preset's value.
How tracks change at a step, keyed by preset ID. in holds the tracks this step turns on or makes louder (Coming in); out the tracks the next step turns off or makes quieter (Leaving).
"transitions": {
"full_storm": {
"in": { "Thunder/Thunder 1": { "styles": ["dist"], "values": { "dist": 70 }, "pre_fade": 20 } },
"out": { "Gusts": { "styles": ["swell"], "values": { "swell": 20 }, "pre_fade": 0 } }
}
}| Style | Coming in | Leaving |
values entry |
|---|---|---|---|
gusts |
Gusts | Gusts dying down | Intensity, 0 to 100 (default 50) |
swell |
Swell | Smooth fade | Speed: seconds of the fade time it uses (default 20) |
build |
Build | Thin out | Amount, 0 to 100 (default 60). For Intermittent tracks. |
dist |
Distance | Recede | How far, 0 to 100 (default 50) |
fresh |
Start fresh | Finish then stop | none |
| Field | Type, range | What it does |
|---|---|---|
styles |
list of the style keys | Any combination. An empty list with pre_fade 0 is Smooth: the track follows the ramp. |
values |
object | The setting of each style that has one. |
max_volume |
0.0 to 1.0, optional | Gusts: the peaks never go above this. |
pre_fade |
0 to 90 seconds | Pre-fade: the change starts this long before the step. The fade time is pre-fade plus ramp. |
The web UI and the API refuse values outside these ranges. Sonorium doesn't check a hand-written presets.json the same way, so keep every value inside its range.
Both are optional and travel with the theme in exports. Sonorium's mixer reads neither.
ATTRIBUTION.md is for people: where the sounds come from, the licence and what it allows, and credits per file.
MANIFEST.json is for tools. The bundled themes use this shape:
{
"source": { "site": "", "url": "", "creator": "The theme author" },
"license": { "name": "CC BY 4.0", "url": "https://creativecommons.org/licenses/by/4.0/", "requires_attribution": true }
}| Field | What it is |
|---|---|
source.site, source.url, source.creator
|
Where the sounds come from and who made them |
license.name, license.url
|
The licence |
license.requires_attribution |
true when anyone sharing the sounds must credit the source |
Sonorium loads every theme when it starts, after Refresh on the Themes page, and after an import.
Each conversion runs once per theme and first copies metadata.json and presets.json to backup files next to them:
| Found | What Sonorium does | Backup |
|---|---|---|
spec_version missing or below 2, or presets inside metadata.json
|
Moves the presets to presets.json and writes "spec_version": 2
|
metadata.json.pre-presets.bak |
No "ebb_duration": true
|
Turns every Ebb & Flow presence around (0.3 becomes 0.7), in the theme and its presets |
*.pre-duration.bak |
A group with "mode": "intrusion"
|
Ungroups its linked files and removes the group; its own files move to the theme folder | *.pre-links.bak |
A track with "playback_mode": "auto" (and no "auto_retired": true) |
Gives it the mode it plays as | *.pre-auto.bak |
A theme written as described on this page (spec_version 2, the three markers true, a mode on every track) needs none of these, and no backup files appear.
A metadata.json or presets.json that isn't valid JSON is renamed to <name>.broken-<date>-<time> and replaced:
-
metadata.jsonis rebuilt with default settings for every track, keeping theidif it can still be read. -
presets.jsonis refilled frommetadata.json.pre-presets.bakif that holds presets, otherwise left empty.
The theme still loads and plays. Fix the .broken file and copy it back over the new one.
Sonorium writes what it did to its log. To read it:
- Click Settings in the menu.
- Click Logs.
Lines to look for:
Theme 'Broken': can't read metadata.json (line 1): Illegal trailing comma before end of object, kept as metadata.json.broken-20261012-112248. Using defaults
Theme 'Old' converted to the 2.0 format
Theme 'Old': Ebb & Flow Interval values converted to Duration (originals kept as *.pre-duration.bak)
Theme 'Old': Auto playback modes replaced by the mode each track plays as (originals kept as *.pre-auto.bak)
Imported theme 'Thunderstorm' with 12 files
The API lists the same problems per theme: GET /api/themes gives each theme a "problems" list, empty when it loaded cleanly.
- Files and folders starting with
.or_, and folders more than one level deep. - Entries in
trackswhose key matches no file or link (kept in the file, not used). - Sequence steps that name a preset that doesn't exist.
Two tracks and one preset: a fire that plays all the time and crickets now and then.
Campfire/
metadata.json
presets.json
Fire.wav
Crickets.wav
metadata.json:
{
"spec_version": 2,
"id": "0b6f2d4e-7a51-4c3e-9d27-5e8a1f3c9b62",
"name": "Campfire",
"description": "A fire crackling, crickets now and then.",
"icon": "🔥",
"categories": ["Nature"],
"ebb_duration": true,
"intrusions_retired": true,
"auto_retired": true,
"tracks": {
"Fire": { "playback_mode": "continuous", "volume": 0.8 },
"Crickets": { "playback_mode": "sparse", "volume": 0.5, "presence": 0.6 }
}
}presets.json:
{
"presets": {
"late_night": {
"name": "Late night",
"is_default": true,
"tracks": {
"Fire": { "volume": 0.5 },
"Crickets": { "volume": 0.7, "presence": 0.9 }
}
}
}
}Without a preset, the fire plays at 80 % and the crickets about every 10 to 18 minutes. Late night (the default) turns the fire down and brings the crickets back about every 4 to 7 minutes. Replace the id with a new UUID of your own.
Rain all the time, a Merry-go-round of wind, thunder in an Intermittent group, drips now and then, and gusts that rise and fall. Two presets and a sequence that moves between them.
Thunderstorm/
metadata.json
presets.json
ATTRIBUTION.md
MANIFEST.json
Rain.wav Background
Drips.wav Intermittent
Gusts.wav Ebb & Flow, with Pause between plays
Wind/ Merry-go-round group
Wind 1.wav
Wind 2.wav
Wind 3.wav
Thunder/ Intermittent group
Thunder 1.wav
Thunder 2.wav
metadata.json:
{
"spec_version": 2,
"id": "c3a9e1f0-5d2b-4f6a-8e47-1b9d0c7a2e58",
"name": "Thunderstorm",
"description": "Rain and wind, with thunder rolling in.",
"icon": "⛈️",
"categories": ["Weather"],
"short_file_threshold": 15.0,
"ebb_duration": true,
"intrusions_retired": true,
"auto_retired": true,
"tracks": {
"Rain": { "playback_mode": "continuous", "volume": 0.7 },
"Drips": { "playback_mode": "sparse", "volume": 0.4, "presence": 0.8, "fade_in": 0.5, "fade_out": 0.5 },
"Gusts": { "playback_mode": "presence", "volume": 0.9, "volume_low": 0.2, "presence": 0.4, "rise": 0.7,
"pause_between": true, "pause_min": 20, "pause_max": 90 },
"Wind/Wind 1": { "playback_mode": "continuous", "volume": 1.0, "presence": 1.0 },
"Wind/Wind 2": { "playback_mode": "continuous", "volume": 0.9, "presence": 1.0 },
"Wind/Wind 3": { "playback_mode": "continuous", "volume": 1.0, "presence": 0.5 },
"Thunder/Thunder 1": { "playback_mode": "sparse", "volume": 1.0, "presence": 1.0 },
"Thunder/Thunder 2": { "playback_mode": "sparse", "volume": 0.8, "presence": 0.6 }
},
"groups": {
"Wind": { "mode": "merry_go_round", "crossfade": 8, "volume": 0.6 },
"Thunder": { "mode": "intermittent", "gap_min": 60, "gap_max": 240, "volume": 0.9, "presence": 1.0 }
},
"attribution": {
"source": "Recorded by the theme author",
"license": "CC BY 4.0",
"license_url": "https://creativecommons.org/licenses/by/4.0/"
}
}presets.json:
{
"presets": {
"light_rain": {
"name": "Light rain",
"is_default": true,
"tracks": {
"Rain": { "volume": 0.5, "muted": false },
"Drips": { "muted": false },
"Gusts": { "muted": true },
"Thunder/Thunder 1": { "muted": true },
"Thunder/Thunder 2": { "muted": true }
},
"groups": {
"Wind": { "volume": 0.3, "muted": false },
"Thunder": { "muted": true }
}
},
"full_storm": {
"name": "Full storm",
"is_default": false,
"tracks": {
"Rain": { "volume": 1.0, "muted": false },
"Drips": { "muted": true },
"Gusts": { "muted": false, "volume": 1.0, "volume_low": 0.4 },
"Thunder/Thunder 1": { "muted": false },
"Thunder/Thunder 2": { "muted": false }
},
"groups": {
"Wind": { "volume": 0.8, "muted": false },
"Thunder": { "muted": false, "gap_min": 30, "gap_max": 120 }
}
}
},
"sequence_defaults": { "hold_min": 300, "hold_max": 900, "ramp": 30 },
"sequences": {
"storm_builds": {
"name": "Storm builds",
"presets": ["light_rain", "full_storm"],
"at_end": "reverse",
"hold_min": 600,
"hold_max": 1200,
"ramp": 60,
"owner": null,
"transitions": {
"full_storm": {
"in": { "Thunder/Thunder 1": { "styles": ["dist"], "values": { "dist": 70 }, "pre_fade": 20 } },
"out": { "Gusts": { "styles": ["swell"], "values": { "swell": 20 }, "pre_fade": 0 } }
}
},
"while": {
"full_storm": {
"tracks": { "Rain": { "volume": 0.9 } },
"groups": {}
}
}
}
}
}ATTRIBUTION.md:
# Thunderstorm
All sounds recorded by the theme author.
Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Credit the author when you share it.
MANIFEST.json:
{
"source": { "site": "", "url": "", "creator": "The theme author" },
"license": { "name": "CC BY 4.0", "url": "https://creativecommons.org/licenses/by/4.0/", "requires_attribution": true }
}What you hear:
- Light rain (the default, ★): soft rain, a quiet wind bed, drips now and then. Gusts and thunder are off.
- Full storm: loud rain, the wind bed louder, gusts rising and falling between 40 % and 100 % with pauses of 20 to 90 seconds, thunder every 30 seconds to 2 minutes after the last crack, one crack at a time. Drips are off.
- Storm builds: 10 to 20 minutes on each preset with a one-minute glide between them, back and forth. Coming into Full storm, the first thunder starts far off and muffled (Distance), 20 seconds before the step; leaving it, the gusts fade out on a curve (Smooth fade). While Full storm plays in this sequence, the rain is at 90 % instead of 100 %.
In the Wind group, Wind 3 (presence 0.5) comes up less often than the other two. Every wind file is 20 seconds or longer, at least twice the 8-second crossfade.
Both examples were checked on Sonorium 1.7.1: imported as zip files, they load without problems, their presets apply, and the sequence runs and steps.
The zip holds one folder, the theme folder, with everything inside it:
Thunderstorm.zip
Thunderstorm/
metadata.json
presets.json
Rain.wav
...
Wind/Wind 1.wav
...
- Zip the folder, not its contents. In a zip without the top folder, files in group folders land at the top of the theme and the groups are lost.
- The top folder's name becomes the theme folder's name. If a folder with that name already exists, the new one gets
_1(Thunderstorm_1),_2, and so on. - The imported theme gets a new
id.
To make the zip:
- Windows: right-click the theme folder, then click Compress to ZIP file (Windows 11) or Send to › Compressed (zipped) folder.
- macOS: right-click (or Control-click) the theme folder, then click Compress.
-
Linux:
zip -r Thunderstorm.zip Thunderstorm
- Click Themes in the menu.
- Click ⋯ at the top right of the Themes page (or press Tab until it is highlighted, then Enter).
- Click Import theme….
- Pick the zip file and click Open (or type its name and press Enter).
A message shows Imported "Thunderstorm" (12 files), and the theme appears in the list.
In 1.7.1, Import theme works on the Home Assistant app and Docker. On the Windows app, copy the folder in instead (see Copy the folder in).
curl -F "file=@Thunderstorm.zip" http://<sonorium-address>:8008/api/themes/import
curl -X POST http://<sonorium-address>:8008/api/themes/refresh
The first call answers:
{"status": "ok", "theme_folder": "Thunderstorm", "files_extracted": 12, "path": "/media/sonorium/Thunderstorm", "problems": []}The second rescans the audio folder, as the web UI does after an import. With Require login on, add a key with Themes and presets › Upload and delete checked: -H "Authorization: Bearer <key>". Keys are made on Settings › Keys (see Sign-in and Users).
To add one audio file to an existing theme, into a group (the group folder is created if needed):
curl -F "file=@Drip 2.wav" -F "group=Thunder" http://<sonorium-address>:8008/api/themes/<theme id>/upload
A new file starts Unassigned: it doesn't play until you give it a mode in the Theme Editor. A file in a group plays the group's way.
- Copy the theme folder into the audio folder (see Where themes live).
- In Sonorium, click Themes in the menu.
- Click ⋯ at the top right of the Themes page.
- Click Refresh.
A copied theme keeps its id. Don't copy the same theme in twice with the same id: give the copy a new one.
-
Names: name files for what they are (
Rain on roof.wav,Thunder 2.wav). The file name, without its extension, is the track's name in Sonorium and its key in the JSON files. Renaming a file later means changing its key everywhere it appears. -
Levels: Sonorium raises the whole mix (Settings › Audio Settings › Master output gain, 60 % by default) and softens peaks that go too high. With the default gain, a single track's peaks above about -18 dBFS reach that limiter. Keep files modest and balance them with
volumeinstead of making each file loud. -
Loop seams (Background): a Background track crossfades its end into its start over 1.5 seconds, which hides a slightly rough loop point. Turn on
seamless_loop(Gapless) only for a file cut to loop perfectly. Beds of 2 to 5 minutes repeat less noticeably than short ones. - Variety: for a bed that never repeats the same way, use 3 or more files of the same scene in a Merry-go-round group, each at least twice the crossfade long. For events (thunder, a door, a lute song), put several versions in an Intermittent group: they play one at a time, never the same one twice in a row.
-
Abrupt files: give a file that starts or ends with a click a short
fade_in/fade_out(0.2 to 1 second). - File sizes: 44.1 kHz mono WAV is about 5 MB a minute; mono MP3 at 128 kbit/s about 1 MB a minute. Sonorium's own stream is 128 kbit/s mono MP3, so higher quality isn't heard. Use MP3 or OGG for long beds.
- Test as you go: after a change by hand, use Refresh, open the theme with Edit, and listen with Preview mix. Check Settings › Logs if the theme looks different from what you wrote.