Turn a long ambient recording into a sample-accurate, seamlessly-looping .m4a suitable for meditation or breathwork apps. The output's loudness and codec are matched to a reference file so it slots into an existing sound library without sticking out.
Two scripts, one human decision in the middle:
scripts/scan.pyanalyzes the source and ranks the calmest N-minute windows by RMS stability, sudden-change absence, and transient density.- You pick a start time from the ranked list.
scripts/cut.pycross-correlates a sample-accurate loop seam, slices, LUFS-matches the reference, and encodes to AAC m4a.
MP3 has built-in decoder padding that creates a perceptible gap at every loop boundary. AAC in an MP4 container (m4a) ships with edit-list "gapless playback" metadata that AVFoundation, modern Apple players, and Flutter's just_audio / audioplayers all honor. Combined with a sample-accurate loop seam from cross-correlation, the result loops without a click.
- macOS, Linux, or WSL
python3≥ 3.11 (uses the standard-librarytomllib)ffmpegandffprobeonPATH- Python deps:
librosa,numpy,scipy,soundfile
brew install ffmpeg # or apt install ffmpeg
pip install -r requirements.txt# 1. Edit defaults.toml so [reference].path points at YOUR reference audio.
$EDITOR defaults.toml
# 2. Scan your source for the calmest 15-minute windows.
python3 scripts/scan.py /path/to/source.wav
# Sample output:
# --- TOP 5 CALM 15-MIN WINDOWS ---
# rank start end rms(dB) rms_cv maxJump(dB) onset score
# 1 43:05.000 58:05.000 -31.19 0.849 14.70 2.478 -3.89
# 2 13:55.000 28:55.000 -31.16 0.850 20.11 2.431 -1.79
# ...
# 3. Cut at the start time you picked.
python3 scripts/cut.py \
--input /path/to/source.wav \
--output /path/to/source-loop.m4a \
--start "43:05.000"defaults.toml holds every default. Each value is commented with what it means and when you'd change it. Common edits:
[reference].path— your library's reference audio (loudness + codec target).[scan].window_min— different target loop length (10, 20, 30 minutes, etc).[encode].bitrate/sample_rate/channels— uncomment to override what the script would otherwise probe from the reference.
CLI flags on either script override the corresponding config value. Use --config /path/to/other.toml to point at a different config file entirely.
For each candidate 15-minute window, scan.py computes three metrics on the audio envelope:
| metric | meaning | why it matters for meditation |
|---|---|---|
rms_cv |
std/mean of per-second RMS | smooth windows have low variance |
maxJump(dB) |
largest dB delta between adjacent 5-second sub-windows | no sudden loud moments |
onset |
mean of librosa.onset.onset_strength |
absence of percussive events |
All three are z-scored across valid windows and summed. Windows whose mean RMS is in the bottom 10% (likely silence/fade) or top 10% (likely loud passages) are excluded. Lower combined score = calmer.
maxJump(dB) is the best perceptual tiebreaker — when two candidates score similarly, the one with the smaller max jump is the one a meditator won't be startled by.
cut.py does NOT just truncate at start + duration. Instead:
- Take the first 5 seconds of the cut as a "fingerprint".
- Search ±10 seconds around
start + durationfor the sample that best matches the fingerprint (via SciPy'ssignal.correlate). - Cut to that exact sample.
The actual output duration may therefore differ from your requested duration by up to ±10 seconds. That's the price of a seam where the waveform at end ≈ waveform at start.
We do NOT crossfade the boundary, because a crossfade would make true-start differ from true-end and defeat the alignment.
cut.py measures integrated loudness (LUFS-I) of both the reference and the temporary cut using ffmpeg's ebur128 filter, then applies a single volume=<gain>dB shift before encoding. No compression, no limiting, no loudnorm — those are dynamic processors and would change the meditative texture. Just a level shift.
After encoding, the output is re-measured and the delta vs the reference is printed. You should see ±0.5 LUFS, often exactly 0.
meditation-loop/
├── .claude/
│ └── skills/meditation-loop/SKILL.md # makes the workflow available to Claude Code
├── scripts/
│ ├── _common.py # parse_time, fmt_time, db, load_config
│ ├── scan.py # Phase 1: find calmest windows
│ └── cut.py # Phase 2: cut, LUFS-match, encode
├── defaults.toml # annotated configuration
├── requirements.txt
├── CLAUDE.md # repo conventions + Python maintenance notes
├── README.md # this file
└── LICENSE
This repo ships with a Claude Code skill at .claude/skills/meditation-loop/. Symlink it into your user skills directory once:
ln -s "$(pwd)/.claude/skills/meditation-loop" ~/.claude/skills/meditation-loopThen in any Claude Code session, asking things like "find a calm 15-min section in <file>.wav and save as m4a" will trigger the skill and walk through the pipeline.
MIT. See LICENSE.