Soundpacks

A soundpack maps Claudio sound keys (such as success/git-success.wav) to audio files. A pack can be a directory, a JSON manifest, or a git repository that Claudio manages for you.

The first half of this page covers using and installing packs. The second covers building one. The last section explains how Claudio picks a sound, which you only need when debugging or naming files precisely.

Audio Formats

Claudio’s native player decodes:

Playback reads at most 100 MiB from any audio file. An MP3 whose decoded PCM would exceed 100 MiB is rejected, so an MP3 well under that size on disk can still hit the limit.

Categories

Every event maps to one category, and each category is a directory in the pack:

Category Typical events Directory
Loading PreToolUse, SubagentStart loading/
Success successful PostToolUse success/
Error failed PostToolUse, PostToolUseFailure error/
Interactive prompts, notifications, permission requests interactive/
Completion Stop, SubagentStop completion/
System session start, compaction system/

default.wav at the pack root is the last fallback for every event.

Where Soundpacks Live

Claudio uses XDG base directories. On Linux and WSL the data directory is ~/.local/share; on macOS it is ~/Library/Application Support; on Windows it is %LOCALAPPDATA%. On macOS and Windows the config directory is the same folder as the data directory.

Item Linux / WSL macOS Windows
Installed packs ~/.local/share/claudio/soundpacks/<name>/ ~/Library/Application Support/claudio/soundpacks/<name>/ %LOCALAPPDATA%\claudio\soundpacks\<name>\
Managed git clones ~/.local/share/claudio/soundpack-repos/<name>/ ~/Library/Application Support/claudio/soundpack-repos/<name>/ %LOCALAPPDATA%\claudio\soundpack-repos\<name>\
Managed git registry ~/.config/claudio/soundpacks.json ~/Library/Application Support/claudio/soundpacks.json %LOCALAPPDATA%\claudio\soundpacks.json
config.json ~/.config/claudio/config.json ~/Library/Application Support/claudio/config.json %LOCALAPPDATA%\claudio\config.json

An installed JSON pack is a directory like any other, with its manifest at soundpacks/<name>/soundpack.json.

XDG_DATA_HOME and XDG_CONFIG_HOME override these locations on every platform, including Windows.

Built-In Packs

Four platform packs are compiled into the binary and need no install step. claudio soundpack list shows them with the type embedded.

Pack What it plays
windows Sounds from C:\Windows\Media
wsl The same Windows sounds, reached through /mnt/c/Windows/Media
darwin macOS system sounds from /System/Library/Sounds
linux Seven synthesized tones shipped inside the binary, one per category plus default.wav

The windows, wsl, and darwin packs only map to files that already ship with the operating system. WSL gets its own pack, even though it runs the Linux binary, because the Windows sound files are available there.

Using A Pack

Switching The Active Soundpack

claudio soundpack list
claudio soundpack use <name>

soundpack use sets default_soundpack in config.json. The name has to be one that soundpack list shows: an embedded pack, a managed git pack, a pack in the XDG soundpacks/ directory, or a pack listed in soundpack_paths. soundpack list prints packs in that order. If two packs share a name, the first one listed is the one Claudio plays.

To override the pack for one run without changing the config:

claudio --soundpack <name>
CLAUDIO_SOUNDPACK=<name> claudio

If the configured pack cannot be found, Claudio logs an error and falls back to the platform pack.

Installing A Pack From Git

A pack is shared as a git repository. Any public or private repository that contains a directory pack or a JSON pack works.

To find published packs:

claudio soundpack search          # every published pack, most starred first
claudio soundpack search retro    # narrowed by words

There is no central index to register with. search lists GitHub repositories tagged with the topic claudio-soundpack and prints the command that installs each one. See Publishing A Pack to make yours show up.

claudio soundpack add https://github.com/owner/repo --name my-pack --default
claudio soundpack add gh:owner/repo --subdir packs/minimal --name minimal

add clones the repository into soundpack-repos/<name>/, validates it, records it in the local soundpacks.json registry (name, source URL, ref, commit), and adds the playable path to soundpack_paths. gh:owner/repo is shorthand for https://github.com/owner/repo.git.

Flag Effect
--name Name for the installed pack
--subdir Directory or JSON file inside the repository to use as the pack
--ref Branch, tag, or commit to check out
--default Make it the active pack
--replace Replace an existing managed pack with the same name
--skip-validate Skip validation before adding

Update one pack or all of them:

claudio soundpack update my-pack
claudio soundpack update --all
claudio soundpack update my-pack --force   # discard local changes in the clone first

Claudio never fetches in the background or while handling hooks. A managed pack stays at the commit it was cloned or last updated to until you run soundpack update. Schedule that yourself if you want it automatic.

Check what is installed:

claudio soundpack status
claudio soundpack status my-pack

Removing A Pack

Managed git packs:

claudio soundpack remove my-pack                # delete the clone and registry entry
claudio soundpack remove my-pack --keep-files   # drop the registry entry, keep the clone
claudio soundpack remove my-pack --force        # drop registry and config entries even if deleting the clone fails

remove also takes the pack out of soundpack_paths, and if it was the active pack, resets default_soundpack to the platform default.

Packs installed with soundpack install have no remove command. Delete the directory:

rm -rf "$XDG_DATA_HOME/claudio/soundpacks/my-pack"   # adjust for your platform

Hooks skip a soundpack_paths entry that no longer exists. The next soundpack install, add, remove, or use drops it from config.json and prints the path it dropped; you can also delete the entry by hand.

If it was the active pack, pick another with claudio soundpack use <name>. Until you do, Claudio logs an error on each event and uses the platform pack.

Building A Pack

Directory Soundpacks

In a directory pack, each sound key is a file path relative to the pack root:

my-pack/
  default.wav
  loading/
    git-commit-start.wav
    git-start.wav
    bash-start.wav
    loading.wav
  success/
    git-commit-success.wav
    git-success.wav
    bash-success.wav
    success.wav
  error/
    git-commit-error.wav
    git-error.wav
    bash-error.wav
    error.wav
  interactive/
    message-sent.wav
    notification.wav
    permission-request.wav
    interactive.wav
  completion/
    agent-complete.wav
    subagent-complete.wav
    completion.wav
  system/
    session-start.wav
    compacting.wav
    post-compact.wav
    system.wav

Sound keys always end in .wav, but the file does not have to. When the exact .wav file is missing, Claudio looks for the same name with .mp3, .aiff, .aif, or .mpeg, so success/success.mp3 answers for success/success.wav.

One file per category plus default.wav is enough to cover every event. Add more specific names as you go; Fallback Chains lists which names Claudio tries for each event.

Validate and install:

claudio soundpack validate ./my-pack
claudio soundpack install ./my-pack --default

install validates the pack, copies it to <XDG_DATA_HOME>/claudio/soundpacks/<name>/, and adds that path to soundpack_paths. --skip-validate skips the coverage check; the copied pack is still checked for safety.

Claudio finds packs in the XDG soundpacks/ directory by name. If a directory there contains soundpack.json, Claudio loads it as a JSON pack; otherwise it reads the category layout above. A pack anywhere else needs an entry in soundpack_paths or a full path. Scans skip .git directories.

JSON Soundpacks

A JSON pack maps sound keys to files stored next to the manifest:

{
  "name": "system-sounds",
  "description": "Small pack using existing local sounds",
  "version": "1.0.0",
  "mappings": {
    "success/success.wav": "./sounds/success.wav",
    "error/error.wav": "./sounds/error.mp3",
    "loading/loading.wav": "./sounds/loading.wav",
    "interactive/message-sent.wav": "./sounds/message-sent.aiff",
    "default.wav": "./sounds/default.wav"
  }
}

Rules:

Create a template:

claudio soundpack init my-pack                  # writes ./my-pack.json
claudio soundpack init my-pack --dir ./packs    # writes ./packs/my-pack.json
claudio soundpack init my-pack --from-platform  # pre-fills the current platform pack's mappings

A plain init template lists every known key with an empty value. Fill in the keys you want; the empty ones are skipped, so you can leave them in or delete them. validate lists them as unmapped.

Validate and install:

claudio soundpack validate ./my-pack.json
claudio soundpack install ./my-pack.json --default

install copies the manifest and every file it references into <XDG_DATA_HOME>/claudio/soundpacks/<name>/, keeping relative subdirectories, writes the manifest there as soundpack.json, and adds it to soundpack_paths. You can then refer to the pack by its directory name or its name field.

Validation

claudio soundpack validate ./my-pack.json
claudio soundpack validate ./my-pack

The report shows:

Broken references fail validation. Empty mappings are reported as unmapped but do not fail it.

validate checks that a pack is complete. It does not open the audio. For that, use audit.

Auditing And Mastering

A pack assembled from many sources has sounds at many levels and lengths. In one real pack the loudest sound was 37 LU louder than the quietest, two “sounds” were three-minute ambience loops, and every MP3 began with 20 to 200 ms of silence. audit finds these problems and master fixes them.

claudio soundpack audit ./my-pack
SOUND                       LENGTH    LUFS    PEAK  HIT    PITCH  TREND    TEXTURE  FINDINGS
error/error.mp3              0.94s   -29.4   -20.1    3   5663Hz  steady   tonal   too-quiet, leading-silence
loading/git-pull-start.mp3   0.34s    -8.4    -0.4    3   1242Hz  steady   tonal   too-loud, peak-over-ceiling, leading-silence
system/session-start.mp3   227.69s   -24.4    -7.6 2405     70Hz  steady   tonal   too-long, too-quiet

Audit decodes each file with the decoders playback uses, so a file it accepts is a file Claudio can play.

Every mapped file in a JSON pack is checked regardless of its extension. Content the playback decoders do not support, such as Ogg Vorbis, produces an undecodable error instead of being skipped. These errors fail the audit with or without --strict.

Column Meaning
LENGTH Duration of the file, silence included.
LUFS Loudness (ITU-R BS.1770, the measure streaming services normalize to). Sounds under 400 ms are too short for the standard’s gating, so they are measured ungated over their whole length. Mono files are measured as they play: on both speakers.
PEAK True peak in dBTP, estimated by 4x oversampling.
HIT Number of separate bursts: 1 for a single tone, 3 for a triple beep. It counts a new hit only after the level has dropped, so notes played over a held or ringing sound, quiet echoes and tremolo are not counted.
PITCH The note: the lowest strong partial. --json also gives the strongest partial, which can be an overtone.
TREND Whether the sound ends on a higher pitch than it starts on (rising), lower (falling), or neither. For noise, which has no pitch, it is whether the second half is brighter or duller than the first.
TEXTURE tonal, mixed, or noisy.

The last four columns describe the sound, so you (or an agent that cannot hear) can tell sounds apart, spot the one file that is not like the others, and check that errors do not sound like successes. They are instruments with limits, not a specification. When a sound is right and a column disagrees for a reason given above, keep the sound and note the reading; do not thin out a ringing chord so that its notes get counted. --json adds attack time, spectral centroid, silence at each end, and a hash per file.

What audit warns about, and why:

Rule Why it matters
too-long A loading sound plays before every tool call. Past about a second it is still playing when the result sound starts. Limits are per category: loading 1s, success 1.5s, default 1.5s, error 2s, interactive 2.5s, completion 3s, system 6s. A file that answers for several categories gets the strictest.
too-loud, too-quiet More than 1 LU from the -18 LUFS target. One volume setting should suit every sound.
peak-over-ceiling True peak above -1 dBTP distorts on some outputs.
clipping Runs of full-scale samples: the source was recorded or exported too hot. Mastering cannot undo this.
leading-silence More than 15 ms of silence before the sound. The sound feels late. MP3 files nearly always have this, because encoders pad the start.
trailing-silence More than 150 ms of silence after the sound. It keeps the player busy for nothing.

Notes do not fail an audit. peak-limited marks a sound (usually a click) that cannot reach the target without exceeding the peak ceiling. duplicate marks files with identical bytes: in a directory pack that is the only way to reuse a sound, in a JSON pack map both keys to one file instead. unreferenced marks audio a JSON pack ships but never maps.

--strict makes warnings fail the command, which is what you want in CI. --target-lufs, --tolerance, --peak-ceiling, and --max-duration loading=800ms,system=4s change the standard.

master writes a copy of the pack that passes:

claudio soundpack master ./raw-pack --out ./my-pack
claudio soundpack audit ./my-pack --strict

For each sound it trims silence from both ends, cuts anything over its category’s limit (with a fade), and applies one gain so the sound sits at the target without its peak crossing the ceiling. For most sounds that is all: nothing is compressed, so the sound keeps its shape. A sound that gain alone leaves short of the target has a few peaks standing far above the rest of it, which is what a recorded knock or click looks like. master holds just those peaks down (by at most 9 dB, starting 2 ms ahead of each one) so the body of the sound can come up, and reports it as peaks limited. You do not need to add drive to a recipe to make a spiky recording loud enough. Output is 48 kHz 16-bit WAV, mono when both channels are identical: the player’s own format, so nothing is resampled at play time and there is no encoder padding.

Sounds reported as truncated lost their end. master keeps the first part of the sound, which is not always the part worth keeping. For those, cut the source file yourself and master again.

A Pack Repository

This layout keeps the raw material, records which sound answers for which key, and makes the published pack a build product:

my-pack/
  source/
    soundpack.json     # manifest over the raw files: key -> raw file
    raw/*.mp3|wav|aiff # anything Claudio can decode, any length or level
  soundpack.json       # built: key -> sounds/...wav
  sounds/              # built: mastered WAVs
  README.md
claudio soundpack master source/soundpack.json --out .
claudio soundpack audit soundpack.json --strict
claudio soundpack validate soundpack.json

The source manifest is an ordinary JSON pack, so it can map many keys to one raw file. master writes each raw file once however many keys use it, and soundpack add picks up the soundpack.json at the repository root. Audio under source/ is not part of the built pack and is not audited with it.

To hold the pack to the standard, run the audit in CI:

# .github/workflows/audit.yml
name: audit
on: [push, pull_request]
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: stable
      - run: go install claudio.click/cmd/claudio@latest
      - run: claudio soundpack validate soundpack.json
      - run: claudio soundpack audit soundpack.json --strict

Publishing A Pack

Three conventions make a pack findable and installable by name:

  1. Name the repository claudio-soundpack-<name>.
  2. Keep the pack at the repository root: soundpack.json, or a directory pack’s category folders.
  3. Add the GitHub topic claudio-soundpack:

    gh repo edit --add-topic claudio-soundpack
    

claudio soundpack search then lists it, and anyone can install it with claudio soundpack add gh:<owner>/claudio-soundpack-<name> --name <name>. Put a one-line description on the repository; it is what search shows.

Synthesizing Sounds

You do not need recordings to make a pack. synth renders sounds from a JSON recipe, so a pack can be written, reviewed and changed as text, and everything in it is your own work.

{
  "name": "my-pack",
  "description": "Soft wooden taps",
  "version": "1.0.0",
  "sounds": {
    "tick": {
      "about": "one short woody tap",
      "layers": [
        {"freq": 900, "freq_end": 500, "dur": 0.03, "decay": 0.03, "sustain": 0},
        {"wave": "noise", "dur": 0.01, "highpass": 2000, "gain": -12}
      ]
    },
    "done": {
      "about": "two rising notes with a little room",
      "layers": [{"wave": "triangle", "notes": ["C5", "G5"], "step": 0.09, "dur": 0.08, "release": 0.12}],
      "effects": [{"type": "reverb", "size": 0.3, "mix": 0.15}]
    }
  },
  "mappings": {
    "default.wav": "tick",
    "loading/loading.wav": "tick",
    "success/success.wav": "done"
  }
}
claudio soundpack synth source/recipe.json --out source   # source/synth/*.wav + source/soundpack.json
claudio soundpack master source/soundpack.json --out .    # soundpack.json + sounds/
claudio soundpack audit soundpack.json --strict

A sound is one or more layers mixed together, then passed through effects in order. Times are in seconds and gains in dB.

Layer setting Meaning
wave sine (default), triangle, square, saw, pulse, or noise.
freq, freq_end Pitch in Hz or as a note name ("A4", "C#5", "Bb3"). freq_end glides to a second pitch over dur.
notes, step A sequence instead of one pitch: each note lasts dur and starts step after the last. 0 is a rest.
at, dur When the layer starts, and how long each note is held before its release.
attack, decay, sustain, release The envelope. Defaults: 5 ms attack, no decay, sustain 1, 50 ms release. decay with "sustain": 0 is a pluck.
gain, pan Level in dB; position from -1 (left) to 1 (right).
harmonics Overtone levels for a sine, e.g. [1, 0.5, 0.25].
width Duty cycle of a pulse wave.
fm {"ratio": 3.5, "index": 4, "index_end": 0}: frequency modulation. Non-integer ratios sound like bells and metal; a falling index sounds struck.
vibrato, tremolo {"rate": 6, "depth": 0.3}. Vibrato depth is in semitones, tremolo depth from 0 to 1.
voices, detune Stacked copies spread over +/- detune cents and across the stereo field.
lowpass, highpass, q Filter cutoffs in Hz and resonance. lowpass_end and highpass_end sweep the cutoff over the note.

A layer can also play a recording instead of an oscillator:

Sample setting Meaning
sample Path to a WAV, MP3 or AIFF file, relative to the recipe. It must stay inside the recipe’s directory.
start, end The slice to play, in seconds into the file. Without end it plays to the end of the file.
speed Plays faster or slower, which moves the pitch too: 2 is an octave up and half as long. To tune a recording to a note, use target Hz divided by its measured Hz.
reverse true plays the slice backwards.
dur Optional. Leave it out to play the whole slice; set it to stop a long ring early, with release as the fade.

A sample layer takes gain, pan, tremolo, the envelope and the filters, mixes with oscillator layers in the same sound, and goes through the sound’s effects. It has no attack and a 10 ms release unless you set them.

Effect type Settings
reverb size (0 to 1), damp, mix
delay time, feedback, mix
drive amount (0 to 1)
bitcrush bits, downsample
lowpass, highpass freq, q

about is a note to yourself about what the sound should be. It is not rendered, but synth prints it beside each sound, and it is what you check the audit’s HIT, PITCH and TREND columns against: a sound described as “three falling notes” should audit as 3 hits, falling.

Rendering is deterministic, and a setting synth does not know is an error rather than something it ignores.

Some things that hold across packs:

Building From Recordings

Most good packs start from recordings. The process:

  1. Download raw material into source/raw/<collection>/. Keep the collection name in the path so it stays obvious where a file came from.
  2. Record where each file came from in source/sources.json as you go, not afterwards:

    {
      "sources": [
        {
          "file": "raw/kenney/impact-sounds/impactWood_medium_000.wav",
          "title": "impactWood_medium_000",
          "author": "Kenney",
          "source": "https://kenney.nl/assets/impact-sounds",
          "license": "CC0 1.0",
          "license_url": "https://creativecommons.org/publicdomain/zero/1.0/"
        }
      ]
    }
    

    A file whose licence you cannot establish does not go in a pack you publish. Credit every author in the README; CC BY requires it.

  3. Find the sounds by measuring: claudio soundpack audit source/raw/<collection> prints length, hits, pitch, trend and texture for every file. Ignore the warnings; raw material is not mastered yet.
  4. Use them from the recipe as sample layers. Every cut, repitch and fade is then written down, and the pack rebuilds from source/recipe.json and source/raw/.
  5. Build and audit as for any pack: synth, master, audit --strict.

Where to get recordings you may redistribute:

Source Licence Notes
Kenney CC0 Designed interface, impact, sci-fi and game sounds, downloadable as zip files. OGG: convert with ffmpeg -i in.ogg out.wav.
Wikimedia Commons Per file Real-world recordings. The API returns each file’s licence and author, so a search can be filtered to public domain, CC0 and CC BY (see below).
Freesound Per file The largest library. Search and download need an API key; filter on license:"Creative Commons 0".
BigSoundBank CC0 Real-world recordings. The site blocks automated clients, so download by hand.

Searching Commons for audio with its licence and author:

curl -s -A "my-pack-tooling/1.0 (https://example.com/contact)" --get \
  "https://commons.wikimedia.org/w/api.php" \
  --data-urlencode "action=query" --data-urlencode "format=json" \
  --data-urlencode "generator=search" --data-urlencode "gsrnamespace=6" \
  --data-urlencode "gsrsearch=filetype:audio singing bowl" \
  --data-urlencode "prop=imageinfo" \
  --data-urlencode "iiprop=url|size|extmetadata" \
  --data-urlencode "iiextmetadatafilter=LicenseShortName|Artist|LicenseUrl"

Keep the response beside the downloads: it is your provenance record. Commons asks for a descriptive User-Agent and rate-limits bulk downloads: fetch one file every few seconds, and check that what you saved is audio, because a throttled request returns an HTML error page with the file’s name on it. Search results are loose (a search for “ratchet” also returns pronunciations and songs), so choose by title and by measuring.

Sounds from films, television and games are copyrighted however they reach you. A pack built from them is yours to use; whether to publish it is a decision to make knowingly, and sources.json should say plainly where each clip came from.

Choosing What To Cut

master handles level and silence. Deciding which two seconds of a long recording to keep needs a look at the sound. If you cannot listen (or you are an agent), render a spectrogram and read it as a picture:

ffmpeg -i source/raw/ambience.mp3 \
  -lavfi "showspectrumpic=s=1200x300:scale=log:fscale=log" ambience.png

Time runs left to right and pitch bottom to top. Beeps show as short bright bars, hum as a band along the bottom, silence as black. Pick the window that holds the event you want, then cut it into source/ and point the source manifest at the cut:

ffmpeg -ss 21.7 -t 5.8 -i ambience.mp3 \
  -af "afade=t=in:d=0.15,afade=t=out:st=5.2:d=0.6" source/raw/session-start.wav

After mastering, run audit again and read the HIT, PITCH and TREND columns to confirm the cut contains what you meant it to.

Using Tracking To Improve A Pack

Sound tracking is on by default. Use Claudio for a while, then list the keys it looked for and did not find:

claudio analyze missing --preset all-time --limit 50

The most frequent missing keys are usually the best sounds to add next.

How Sound Selection Works

Fallback Chains

For each event Claudio builds a list of candidate keys, from most specific to least specific, and plays the first one the pack has. Duplicate candidates are dropped.

PreToolUse

git commit run through the Bash tool:

loading/git-commit-start.wav
loading/git-commit.wav
loading/git-start.wav
loading/git.wav
loading/bash-start.wav
loading/bash.wav
loading/tool-start.wav
loading/loading.wav
default.wav

PostToolUse

A successful git commit:

success/git-commit-success.wav
success/git-success.wav
success/bash-success.wav
success/tool-complete.wav
success/success.wav
default.wav

A failed one uses the error/ category:

error/git-commit-error.wav
error/git-error.wav
error/bash-error.wav
error/tool-complete.wav
error/error.wav
default.wav

The post-tool chain skips the bare command key (success/git.wav), so a generic command sound cannot stand in for a specific result.

Other Events

Events without a tool use a short chain: a specific key, an event key, the category sound, and default.wav.

Event Keys tried before the category sound
UserPromptSubmit interactive/message-sent.wav, interactive/prompt-submit.wav
Notification interactive/notification.wav
PermissionRequest interactive/permission-request.wav
Stop completion/agent-complete.wav, completion/stop.wav
SubagentStop completion/subagent-complete.wav, completion/subagent-stop.wav
SubagentStart loading/subagent-start.wav
SessionStart system/session-start.wav
PreCompact system/compacting.wav, system/pre-compact.wav
PostCompact system/post-compact.wav

For example, Stop tries completion/agent-complete.wav, completion/stop.wav, completion/completion.wav, then default.wav.

Command Parsing

For Bash tool events, Claudio parses the command string and recognizes the subcommands of:

For other commands, Claudio still tries command-level keys such as loading/systemctl-start.wav, and treats the second word as a subcommand (loading/systemctl-restart-start.wav) when it looks like one rather than a file path, flag, or URL.

MCP Tools

MCP tools (names beginning with mcp__) first try the shared mcp keys, then the full normalized tool name. For mcp__github__create_issue at PreToolUse:

loading/mcp-start.wav
loading/mcp.wav
loading/mcp-github-create-issue-start.wav
loading/mcp-github-create-issue.wav
loading/tool-start.wav
loading/loading.wav
default.wav

See Also