acidcat
A pure-Python inspector, editor, and forensic tool for audio files and synth/DAW presets: read the metadata, decode the format structure byte by byte, flag anomalies, repair broken containers, and identify how a file was made.
Reads BPM, key, duration, tags, and format info from WAV, AIFF, MP3, FLAC,
OGG, Opus, M4A, MIDI, and Serum presets. Also structurally decodes Bitwig
(.bwpreset/.bwclip), Native Instruments (Massive/Absynth/Kontakt/NKS/KORE),
Vital, NCW, SoundFont (SF2/SF3), tracker modules (MOD/XM/IT), MP4, VST FXP,
ReCycle RX2, and RMID containers via inspect. Beyond reading: repair and
validate fix and check container structure (stale sizes, offset tables,
counts, pad bytes) without touching a byte of audio, and audit gives a
forensic verdict (structure, integrity, hidden data, and the writing tool).
One pure-Python dependency (mutagen); the native inspect walkers need nothing.
Optional librosa analysis for BPM/key detection and ML feature extraction.
Also ships per-library SQLite indexes (acidcat index) tracked in a
small global registry, plus an MCP server (acidcat-mcp) so an LLM can
query your whole collection across libraries by bpm, key, tags, or
full-text.
Install
git clone https://github.com/hed0rah/acidcat.git
cd acidcat
pip install -e . # core + mutagen (WAV/AIFF/MIDI/Serum/MP3/FLAC/OGG/Opus/M4A)
pip install -e .[analysis] # + librosa BPM/key detection + features
pip install -e .[mcp] # + MCP server (acidcat-mcp, stdio)
pip install -e .[mcp-http] # + MCP streamable-HTTP transport (acidcat-mcp --transport http)
pip install -e .[crypto] # + AES for encrypted Wii disc extraction
pip install -e .[all] # everything
Quick Start
# single file -- instant metadata
acidcat kick_808.wav
acidcat loop.mp3
acidcat pad.flac
# pipe from stdin
cat file.wav | acidcat
curl https://example.com/loop.mp3 | acidcat -
# JSON output for piping
acidcat kick_808.wav -f json | jq .BPM
# deep analysis with librosa
acidcat kick_808.wav --deep
# scan a mixed-format directory
acidcat scan ~/Samples/Breaks -n 200
Supported Formats
| Format | Extension | What acidcat reads |
|---|---|---|
| WAV | .wav |
BPM, key, loop points, beats, ACID/SMPL, LIST/INFO, bext, cart, iXML |
| AIFF | .aif |
Duration, format, name, author, copyright, markers |
| MP3 | .mp3 |
BPM, key, title, artist, album, genre, comment (ID3v2) |
| FLAC | .flac |
BPM, key, title, artist, album, genre (Vorbis Comment) |
| OGG | .ogg |
BPM, key, title, artist, album, genre (Vorbis Comment) |
| Opus | .opus |
BPM, key, title, artist (Vorbis Comment) |
| M4A | .m4a |
BPM, key, title, artist, album, genre (iTunes atoms) |
| MIDI | .mid |
BPM, key sig, time sig, tracks, note count/range |
| RMID | .rmid |
RIFF-wrapped MIDI: RIFF wrapper + the inner SMF (inspect) |
| MIDI 2.0 | .midi2 |
MIDI Clip File: SMF2CLIP magic + UMP stream -- resolution, tempo, time sig, tick-stamped events (inspect) |
| N64 bank | .ctl |
libultra ALBankFile: bank/instrument/wavetable tree + VADPCM codebooks (inspect); core/vadpcm.py decodes N64 vector ADPCM |
| Serum | .SerumPreset |
Preset name, author, tags, description |
| VST FXP | .fxp |
Preset kind, plugin id, version, preset name (inspect) |
| ReCycle | .rx2 |
CAT/REX2 chunks, creator, slice count (inspect) |
| Bitwig WT | .wt |
Wavetable header: frame count, samples/frame, 16-bit sample block (inspect) |
| Bitwig | .bwpreset, .bwclip |
Device tree, parameters, clip notes (inspect + index) |
| Bitwig multisample | .multisample |
Zone map: per-sample root note, key/velocity range, loop (inspect) |
| Native Instruments | .nmsv, .nabs, .ksd, .nksf, .nki |
Preset metadata, NKS tags, FastLZ subtree (inspect + index) |
| Vital | .vital |
Patch name, author, tags, modulation matrix (inspect + index) |
| NCW | .ncw |
NI Compressed Wave header, channel/block info (inspect + convert to WAV) |
| SoundFont | .sf2, .sf3 |
sfbk metadata + every named sample with its byte offset; SF3 = Ogg-Vorbis samples (inspect + convert to WAV/Ogg) |
| Tracker | .mod, .xm, .it |
ProTracker / FastTracker II / Impulse Tracker: header, pattern order, every embedded sample at its byte offset; IT offset tables as pointers (inspect) |
| MP4 | .mp4, .m4a |
Box tree, codec info, iTunes tags, stco/co64 offset tables (inspect + repair) |
Format anatomy
Interactive datasheets for the formats acidcat dissects, each drawn byte by byte: hover a field to light its exact bytes and read the decode, click a field for its table. The RIFF/WAVE family, MP3, FLAC, Ogg, MP4, MIDI, the trackers, the sampler and synth-preset formats, and more, with the history and edge-case notes behind each.
Browse them at hed0rah.github.io/audio_files_anatomy
-- a good start is the WAV / RIFF page,
which walks the container, the fmt/data/smpl/acid chunks byte by byte, and
the whole RIFF-to-BWF-to-RF64-to-Wave64 family.
Commands
| Command | Description |
|---|---|
acidcat FILE |
Show metadata for a single file (auto-detected) |
acidcat DIR |
Batch-scan a directory (auto-detected) |
acidcat - |
Read from stdin |
acidcat info FILE |
Explicit single-file metadata dump |
acidcat scan DIR |
Batch-scan with CSV output |
acidcat chunks FILE |
Walk RIFF chunks -- offsets, sizes, parsed fields |
acidcat survey DIR |
Count chunk types across a directory tree |
acidcat shape DIR |
One-line structural fingerprint per file for specimen-hunting -- pipe to sort | uniq -c to surface rare shapes; --fast (header-only), --anomalies, --format FMT, --coarse |
acidcat detect FILE|DIR |
Estimate BPM/key using librosa |
acidcat features DIR |
Extract 50+ audio features for ML |
acidcat similar FILE |
Find samples that sound like FILE, over the index (index --features first); -n N, --kind, --no-kind-filter, --paths-only |
acidcat dump FILE CHUNK [...] |
Hex-dump specific RIFF chunks |
acidcat od FILE |
Colored objdump-x-style hex view: header bytes plus per-field offset / hex / decoded value, opaque payloads dimmed; --color, --width |
acidcat inspect FILE... [--hex] [--frames] [--only/--exclude IDS] [--full] [--anomalies] [--pretty] [--color] |
Byte-level structural dump (WAV, RF64, AIFF, MIDI, MIDI 2.0 clip (.midi2), N64 bank (.ctl), RMID, Serum, VST FXP, ReCycle RX2, Bitwig WT, MP3, FLAC, OGG, MP4/M4A, Bitwig, Vital, NCW, Native Instruments (Massive/Absynth/Kontakt/NKS/KORE)) with lint warnings. Takes multiple files (each under a File: banner; JSON becomes NDJSON). --frames per-frame/event dump, --only/--exclude select chunks, --hex raw bytes, --full a self-contained JSON dump feeding acidcat explore, --anomalies a forensic scan (trailing data, polyglots, cavities, size mismatches, LSB-stego notice), --pretty a human-friendly metadata view, --verbose a deep deconstruction (Bitwig device tree/parameters/notes, Vital modulation matrix, ...), --color to syntax-highlight |
acidcat index DIR |
Upsert DIR into the global SQLite index |
acidcat query [flags] |
Filter the global index by bpm/key/tag/text |
acidcat query --compatible-with FILE |
Find samples that mix with FILE: harmonic key (Camelot) + compatible tempo (incl. half/double-time) |
acidcat convert FILE [-o OUT] |
Export/transcode: .bwclip -> MIDI, NCW -> WAV (single file or a directory), SF2/SF3 -> a folder of samples, 8SVX -> WAV; --to-pcm decodes an ADPCM or mistagged WAV to plain playable 16-bit PCM (--codec ima to force it) |
acidcat locate BLOB [--mode strict|normal|aggressive] [--analyze] [--transforms] [-v] |
"PhotoRec for audio": find the audio regions in a raw blob or disk image (containers, signatureless raw PCM, headerless MP3 streams) and report them; never writes. --analyze infers PCM geometry, --transforms finds audio hidden under XOR/rotate/nibble-swap, -v shows the evidence. Pipe -f json into carve --batch |
acidcat extract BANK [-o DIR] |
Pull every embedded sample out of a known bank/module as its own WAV: MOD/XM/IT/S3M, Gravis .pat, 8SVX, NCW, SF2/SF3, Bitwig .multisample, Kurzweil .krz, E-mu .e4b/.e5b, MPC .snd. Also rips soundtracks off console disc images: PlayStation/CD-XA (.bin/.img), any .cue (CD-DA), GameCube .iso (HPS/ADX/DTK), Wii .iso (BRSTM, needs [crypto]), N64 .z64/.n64/.v64 ROMs (container-agnostic VADPCM recovery), and SNES .sfc/.smc ROMs (container-agnostic BRR recovery). --json for a manifest |
acidcat write FILE --set field=value |
Edit metadata in place, with a _original backup, -o copy, and --dry-run; custom frames via txxx:NAME=value; Bitwig/NI preset editing (experimental) |
acidcat probe FILE read|scan|find|strings|hexdump|diff|entropy|map ... |
Low-level byte dissection (RE-tool surface): typed read at an offset (read fmt.sample_rate -t u32), value scan, byte-pattern find, strings, hexdump, diff, plus entropy (Shannon curve + histogram) and map (binvis Hilbert byte-map). Addresses can be raw offsets or structural names (chunk / chunk.field) resolved through the walker |
acidcat carve FILE (--chunk ID | --trailing | --offset N [--length N] | --at EXPR | --batch SRC) |
Extract a structurally-identified byte region (a chunk payload, an appended blob, or an explicit/anchored range) to a file or stdout; --batch consumes locate records and cuts every region into a directory |
acidcat repair FILE [--dry-run] [-o OUT] |
Fix stale container sizes, offset tables, table counts, and pad bytes without touching a byte of audio (WAV, RF64, AIFF, MP4, FLAC); keeps a _original backup |
acidcat validate FILE|DIR [-q] |
Read-only structural check with an exit code (0 = all consistent, 1 = any violation); walks a directory tree |
acidcat audit FILE [--json] |
Forensic verdict in four parts: STRUCTURE (repairable inconsistencies), INTEGRITY (fake hi-res, duration mismatch), HIDDEN (concealed/appended data + a carve command), PROVENANCE (the writing tool) |
acidcat tui FILE |
Interactive terminal inspector: goto/search, follow pointers (x), byte map (m), edit fields, and validate/repair (v/r) |
acidcat cover FILE [-o art.jpg] [--set img] [--remove] |
Extract, embed, or remove embedded cover art (MP3/FLAC/MP4/Ogg) |
acidcat explore FILE [-o out.html] |
Build a standalone interactive HTML byte-explorer (hex grid + tinted fields + LSB heat-map) |
Global Flags
-f, --format FMT Output format (default varies by command)
-o, --output FILE Write output to file
-q, --quiet Suppress progress output
-v, --verbose Extra detail
-n, --num N Max files to scan (default: 500)
--has CHUNKS Filter by chunk IDs (comma-separated)
--deep Include librosa analysis
Most commands accept table, json, and csv (default table, but
scan and features default to csv). Two differ: inspect is
table/json, and dump is hex/json.
Dependency Groups
| Group | What it adds | Commands enabled |
|---|---|---|
| (none) | mutagen (base) | info, scan, chunks, survey, dump, inspect, explore, index, query, write, convert, cover for WAV/AIFF/MIDI/Serum/MP3/FLAC/OGG/Opus/M4A + all inspect-only formats |
[analysis] |
librosa, numpy, scipy, soundfile | detect, features, info --deep |
[mcp] |
mcp SDK | acidcat-mcp stdio server |
[mcp-http] |
starlette + uvicorn | acidcat-mcp --transport http (streamable-HTTP transport) |
[crypto] |
cryptography | extract audio from encrypted Wii disc images |
[all] |
everything (includes [mcp-http], [crypto]) |
all commands, all formats |
Examples
Metadata Exploration
# what chunks exist in your sample library?
acidcat survey ~/Samples/Loops -n 5000
# walk all chunks in a specific file
acidcat chunks ~/Samples/Loops/breakbeat.wav
# hex-dump the ACID and SMPL chunks
acidcat dump ~/Samples/Loops/breakbeat.wav acid smpl
# fingerprint a whole tree, then rank the rarest structural shapes
acidcat shape ~/Samples --no-path | sort | uniq -c | sort -n
# colored objdump-x-style hex view of a file's headers and fields
acidcat od ~/Samples/Loops/breakbeat.wav
# scan only files with ACID metadata
acidcat scan ~/Samples/Loops --has acid -n 200
# scan a directory with mixed formats (WAV, MP3, FLAC, etc.)
acidcat scan ~/Samples -n 500
BPM / Key Detection
# estimate BPM/key with librosa (for files without metadata)
acidcat detect ~/Samples/OneShots
# scan with librosa fallback for missing metadata
acidcat scan ~/Samples/Loops --fallback -n 100
ML Feature Extraction
# extract 50+ audio features to CSV
acidcat features ~/Samples/Loops -n 500
Recovery / rescue (PhotoRec for audio)
Find audio in a raw blob, cut it out, and make odd codecs playable. Four verbs
chain like coreutils: locate (find) -> carve (cut) -> convert (transcode),
with extract for known banks. Full workflow in docs/recovery.md.
# find the audio regions in a disk image or card dump
acidcat locate disk.img --mode aggressive --analyze
# the pipeline: locate the regions, carve every one into a directory
acidcat locate disk.img -f json | acidcat carve disk.img --batch - -o recovered/
# pull every sample out of a sampler bank / tracker module
acidcat extract kit.sf2 -o kit_samples/
# decode an ADPCM or mistagged WAV to plain playable PCM
acidcat convert weird.wav --to-pcm -o plain.wav
# CTF: find audio hidden under a reversible transform (XOR / rotate / nibble-swap)
acidcat locate challenge.bin --transforms
Similarity Search
Index a library with feature vectors, then find sounds like a reference:
acidcat index ~/Samples/Loops --features # store the vectors
acidcat similar ~/Samples/kick.wav -n 10 # nearest neighbours
similar scores z-standardized cosine over the feature vectors across every
registered library, filtered to the target's kind (loop / one-shot) by default
(--no-kind-filter to disable). The same ranking is available to an LLM through
the MCP find_similar tool -- both call one core implementation, so the CLI and
MCP never drift. If the reference is not indexed, its vector is extracted live
(needs [analysis]).
Libraries (per-directory indexes)
acidcat scan writes a one-off CSV. acidcat index is the persistent
path: each directory you index becomes a library with its own SQLite
file, and a small global registry at ~/.acidcat/registry.db lets reads
fan out across every library you have registered.
By default the per-library DB lives centrally at
~/.acidcat/libraries/<label>_<hash>.db. Pass --in-tree if you'd
rather have the DB travel with the data at
<library>/.acidcat/index.db.
# register and index a library (label defaults to basename of DIR)
acidcat index ~/Samples/Loops --label loops
acidcat index ~/Samples/OneShots --label oneshots
# show every registered library
acidcat index --list
# per-library stats
acidcat index --stats loops
# extract librosa features during indexing (slower, enables similarity)
acidcat index ~/Samples/Loops --label loops --features
# rebuild a library's DB from scratch
acidcat index ~/Samples/Loops --label loops --rebuild
# forget a library (registry only) vs remove it (deletes the DB file)
acidcat index --forget loops
acidcat index --remove loops
# list registered libraries whose DB file is missing on disk
acidcat index --orphans
# import a legacy <name>_tags.json into a library
acidcat index ~/Samples --label samples --import-tags old_tags.json
Nested libraries are rejected at registration time: if you've registered
~/Samples, you can't also register ~/Samples/Loops until you forget
the parent.
Discovery
For users with many scattered packs, --discover walks a tree and
registers every qualifying subdirectory as its own library in one pass.
# preview what would get registered (no writes)
acidcat index --discover ~/Samples --dry-run
# actually register them
acidcat index --discover ~/Samples
# tighter threshold and namespacing for a subset of your collection
acidcat index --discover /mnt/external/old_drives \
--min-samples 50 --label-prefix "ext_"
A directory qualifies if its subtree (within --max-depth, default 3)
contains at least --min-samples audio files (default 20). Non-
qualifying parents are recursed into so packs nested inside catch-all
folders still surface. Already-registered roots are skipped. The home
directory is refused as a discover root to prevent runaway registration.
Querying
By default acidcat query fans out across every registered library and
merges the results.
acidcat query --bpm 120:130 --key Am
acidcat query --tag drums --tag punchy --duration :1
acidcat query --text "dusty lofi" --limit 20
acidcat query --format mp3 --root loops
acidcat query --root loops,oneshots --bpm 128
acidcat query --bpm 128 --paths-only | xargs -I {} cp {} out/
--root accepts a label, an absolute path, or a comma-separated list.
Override the registry on any command with --registry PATH or the
ACIDCAT_REGISTRY environment variable.
MCP Server
acidcat-mcp is a stdio MCP server that exposes the registered libraries
as structured tools. An LLM can ask "what libraries do I have?",
search across them by metadata, find compatible keys via Camelot, or
(with [analysis] installed) find similar samples by librosa feature
cosine.
pip install -e .[mcp] # minimum for discovery + writes
pip install -e .[analysis,mcp] # unlock find_similar / analyze_*
Claude Desktop / Claude Code config:
{
"mcpServers": {
"acidcat": {
"command": "acidcat-mcp"
}
}
}
Optional: pass --registry PATH on the server process or set
ACIDCAT_REGISTRY if your registry lives outside the default location.
Tool tiers (each tool description starts with Fast., SLOW., or
VERY SLOW. so the model self-selects):
- Fast (SQLite only):
search_samples,get_sample,locate_sample,list_libraries,list_tags,list_keys,list_formats,index_stats,find_compatible - Slow analysis (needs
[analysis]):find_similar,analyze_sample,detect_bpm_key - Index management:
reindex,reindex_features,discover_libraries - Write (marked destructive):
register_library,forget_library,tag_sample,set_sample_description
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acidcat-0.85.0.tar.gz.
File metadata
- Download URL: acidcat-0.85.0.tar.gz
- Upload date:
- Size: 653.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
560c5eb2564a772aa69d1b1be3c920df5d8172376c07c54048439b014990b717
|
|
| MD5 |
2183c829a5b6939e7923b8e2bafa351f
|
|
| BLAKE2b-256 |
a0001395d427b4c233eef3362e81064aa151d4b31d368c11e0c0134c1022141d
|
Provenance
The following attestation bundles were made for acidcat-0.85.0.tar.gz:
Publisher:
publish.yml on hed0rah/acidcat
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acidcat-0.85.0.tar.gz -
Subject digest:
560c5eb2564a772aa69d1b1be3c920df5d8172376c07c54048439b014990b717 - Sigstore transparency entry: 2281741665
- Sigstore integration time:
-
Permalink:
hed0rah/acidcat@8aadf76f4b4289ce4220cc5144d0102ce445e0d0 -
Branch / Tag:
refs/tags/v0.85.0 - Owner: https://github.com/hed0rah
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8aadf76f4b4289ce4220cc5144d0102ce445e0d0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file acidcat-0.85.0-py3-none-any.whl.
File metadata
- Download URL: acidcat-0.85.0-py3-none-any.whl
- Upload date:
- Size: 517.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7357655f68cfc96d7ca0a9e5f96a32956b6f52ffd6eed62f009653cadffd3c70
|
|
| MD5 |
5dc081b1e76f4b983a779fba7651b56f
|
|
| BLAKE2b-256 |
c2476de6daa4da132b1931d43e764b1193d376307ea1f08c6dbf50436f18719d
|
Provenance
The following attestation bundles were made for acidcat-0.85.0-py3-none-any.whl:
Publisher:
publish.yml on hed0rah/acidcat
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acidcat-0.85.0-py3-none-any.whl -
Subject digest:
7357655f68cfc96d7ca0a9e5f96a32956b6f52ffd6eed62f009653cadffd3c70 - Sigstore transparency entry: 2281741676
- Sigstore integration time:
-
Permalink:
hed0rah/acidcat@8aadf76f4b4289ce4220cc5144d0102ce445e0d0 -
Branch / Tag:
refs/tags/v0.85.0 - Owner: https://github.com/hed0rah
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8aadf76f4b4289ce4220cc5144d0102ce445e0d0 -
Trigger Event:
release
-
Statement type: