Ffmpeg audio fx
Creative and restoration audio effects with ffmpeg: chorus, flanger, aphaser, tremolo, vibrato, aecho, crystalizer, adecorrelate, adeclick, adeclip, apsyclip, asoftclip, speechnorm, dialoguenhance, adynamicequalizer, adynamicsmooth, afreqshift, aphaseshift, asupercut, asuperpass, asuperstop, atilt, afftfilt, bandpass, bandreject, biquad, allpass, ladspa, lv2. Use when the user asks to add echo/chorus/flanger, create psychedelic audio effects, enhance dialogue, de-click vinyl rips, soft-clip audio, apply LADSPA or LV2 plugins, or shift frequency.From its SKILL.md
npx -y skills add damionrashford/media-os --skill ffmpeg-audio-fxAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.8 KB, as published. Nobody here has run it
ffmpeg Audio FX
Context: $ARGUMENTS
Creative and restoration audio effects. -af chain syntax. Every recipe below is verified against the FFmpeg filters doc. For basic gain / EQ / loudnorm / tempo see the sibling ffmpeg-audio-filter skill — this one is modulation, delay, restoration, true-peak limiting, dynamic EQ, spectral domain, and plugin hosts.
Quick start
- Add echo — Step 2 (Delay group), recipe
aecho. - Chorus / flanger / phaser / tremolo / vibrato — Step 2 (Modulation group).
- Normalize speech for a podcast — Step 2 (Dynamics group), recipe
speechnorm. - Rescue vinyl rip — Step 2 (Restoration group), chain
adeclick→adeclip. - Broadcast-safe true-peak limit —
apsyclip(Dynamics group). - Host a LADSPA or LV2 plugin — Step 2 (Plugin group).
- Shift frequencies (not pitch) —
afreqshift(Filter-curve group).
When to use
- Music production: add modulation (chorus/flanger/phaser), widen a mono source, add shimmer (crystalizer), analog-warm limiting (asoftclip).
- Post / dialogue: enhance voice in a 5.1 mix (dialoguenhance), level an uneven interview (speechnorm), duck resonant frequencies (adynamicequalizer).
- Restoration: vinyl / shellac / cassette rips (adeclick + adeclip + asupercut rumble filter), decorrelate stereo (adecorrelate).
- Broadcast / streaming: psychoacoustic true-peak limiter (apsyclip) as the final stage before dithering to s16.
- DSP / research: arbitrary biquads, spectral-domain manipulation (afftfilt), host third-party plugins via LADSPA / LV2.
Step 1 — Classify the effect
Group the request into one of five buckets before picking a filter:
| Group | Filters | Use when |
|---|---|---|
| Modulation | chorus, flanger, aphaser, tremolo, vibrato | Add movement / wobble / thickness. |
| Delay | aecho | Echo / slap-back / doubler. |
| Dynamics | speechnorm, dialoguenhance, adynamicequalizer, adynamicsmooth, asoftclip, apsyclip, crystalizer | Level, duck, saturate, limit, sharpen transients. |
| Restoration | adeclick, adeclip, adecorrelate, asupercut | Vinyl pops, clipped peaks, stereo smear, subsonic rumble. |
| Filter-curve / spectral | bandpass, bandreject, biquad, allpass, asuperpass, asuperstop, atilt, afreqshift, aphaseshift, afftfilt | Surgical EQ, high-order filters, frequency shifting (not pitch), arbitrary spectral math. |
| Plugin host | ladspa, lv2 | Load an external DSP plugin. Requires build flags. |
Step 2 — Build the filter chain
Copy the exact recipe. Parameters that look arbitrary (e.g. 0.8:0.9:1000:0.3) are positional and mismatched counts produce cryptic errors.
Modulation
# Echo (doubler / slap-back) — delays are MILLISECONDS
ffmpeg -i in.wav -af "aecho=0.8:0.9:1000:0.3" out.wav
# Chorus (6 positional args: in-gain:out-gain:delays-ms:decays:speeds:depths)
ffmpeg -i in.wav -af "chorus=0.7:0.9:55:0.4:0.25:2" out.wav
# Flanger (jet-swoosh; depth is ms range of the LFO)
ffmpeg -i in.wav -af "flanger=delay=5:depth=2:speed=0.5" out.wav
# Phaser (type=t triangle, s sine, n analog noise)
ffmpeg -i in.wav -af "aphaser=type=t:speed=2" out.wav
# Tremolo (volume modulation)
ffmpeg -i in.wav -af "tremolo=f=5:d=0.5" out.wav
# Vibrato (pitch modulation via delay)
ffmpeg -i in.wav -af "vibrato=f=7:d=0.5" out.wav
Dynamics
# Intelligent speech leveler — better than dynaudnorm for voice-only
ffmpeg -i podcast.wav -af "speechnorm=e=12.5:r=0.0001:l=1" out.wav
# Dialogue enhance (boosts the center channel voice band, REQUIRES 5.1+)
ffmpeg -i film.wav -af "dialoguenhance" out.wav
# Dynamic EQ — duck resonant 1 kHz band by 5 dB when it gets loud
ffmpeg -i in.wav -af "adynamicequalizer=bandwidth=300:dfrequency=1000:direction=below:mode=cut:makeup=5" out.wav
# Crystalizer (add harmonics / transient sharpening) — intensity > 3 = artificial
ffmpeg -i in.wav -af "crystalizer=i=2" out.wav
# Soft-clip (warm limiter; type= hard / tanh / atan / cubic / exp / alg / quintic / sin / erf)
ffmpeg -i in.wav -af "asoftclip=type=tanh" out.wav
# Psychoacoustic true-peak limiter (THE recommended broadcast limiter)
ffmpeg -i in.wav -af "apsyclip=level_in=1:level_out=1:clip=0.9" out.wav
Restoration
# Vinyl de-click (arorder 6-8 for vinyl; method a=autoregressive)
ffmpeg -i vinyl.wav -af "adeclick=window=55:overlap=75:arorder=8:threshold=2:burst=2" out.wav
# De-clip (threshold 10+ for obvious clipping; hsize=1000 history size)
ffmpeg -i clipped.wav -af "adeclip=window=55:overlap=75:arorder=8:threshold=10:hsize=1000:method=a" out.wav
# Subsonic rumble kill (sharp 20 Hz high-pass, 20th order Butterworth)
ffmpeg -i in.wav -af "asupercut=cutoff=20:order=20" out.wav
# Stereo decorrelation (adds a tiny inter-channel delay)
ffmpeg -i mono_ish.wav -af "adecorrelate" out.wav
Filter-curve / spectral
# Band-pass for voice intelligibility (Q=2 around 1 kHz)
ffmpeg -i in.wav -af "bandpass=f=1000:width_type=q:w=2" out.wav
# Frequency shift 50 Hz (NOT pitch shift — inharmonic, metallic)
ffmpeg -i in.wav -af "afreqshift=shift=50" out.wav
# Spectral tilt (lean EQ, +/- dB per octave)
ffmpeg -i in.wav -af "atilt=freq=1000:slope=-3" out.wav
# Arbitrary biquad IIR (coefficients b0,b1,b2,a0,a1,a2)
ffmpeg -i in.wav -af "biquad=b0=0.5:b1=0:b2=0.5:a0=1:a1=0:a2=0" out.wav
# FFT filter — real/imag expressions in spectral domain (null example)
ffmpeg -i in.wav -af "afftfilt=real='hypot(re,im)*sin(0)':imag='hypot(re,im)*cos(0)'" out.wav
Plugin host
# LADSPA: file=libname (no .so), plugin=label, c=key=val,...
ffmpeg -i in.wav -af "ladspa=file=cmt:plugin=amp_stereo:c=gain=10" out.wav
# LV2: p=plugin URI, c=key=val,...
ffmpeg -i in.wav -af "lv2=p=http://calf.sourceforge.net/plugins/Reverb" out.wav
Step 3 — Run
Encode to the target codec in the same invocation:
ffmpeg -i in.wav -af "speechnorm=e=12.5:r=0.0001:l=1,apsyclip=clip=0.95" \
-c:a libopus -b:a 96k out.opus
For video input, pass the video through untouched with -c:v copy:
ffmpeg -i in.mp4 -af "dialoguenhance,speechnorm=e=6.25" \
-c:v copy -c:a aac -b:a 192k out.mp4
Step 4 — A/B verify
Always listen to the result against the source. Use the ffmpeg-playback skill's ffplay recipes or diff loudness:
ffmpeg -i in.wav -af "ebur128=peak=true" -f null - 2>&1 | tail -20
ffmpeg -i out.wav -af "ebur128=peak=true" -f null - 2>&1 | tail -20
Target: integrated loudness inside spec (podcast -16 LUFS, streaming -14 LUFS, broadcast -23 LUFS), true peak ≤ -1 dBTP.
Available scripts
scripts/afx.py— stdlib argparse front-end with subcommands for every recipe above.--dry-runprints the ffmpeg command without running it.--verboseshows the full argv.
uv run ${CLAUDE_SKILL_DIR}/scripts/afx.py echo --input in.wav --output out.wav --delay-ms 600 --decay 0.4
uv run ${CLAUDE_SKILL_DIR}/scripts/afx.py deckick --input vinyl.wav --output clean.wav
uv run ${CLAUDE_SKILL_DIR}/scripts/afx.py limit --input master.wav --output final.wav --method psy
Reference docs
Read references/filters.md for per-filter option tables, effect-type taxonomy, LADSPA/LV2 plugin discovery, and production recipe book (vinyl chain, voice chain, radio master chain).
Gotchas
aechodelays are milliseconds in a pipe-separated string:"1000|1500|2000"means three echoes at 1 s / 1.5 s / 2 s. Decays list must match the delay count.choruspositional args arein-gain:out-gain:delays-ms:decays:speeds:depths. Mismatched list counts produce a silent error / empty output.flangerdepthis the LFO oscillation range in milliseconds — typical 0.5–10. Higher sounds like pitch-bend seasickness.aphasertype=ttriangle LFO,ssine,nanalog noise — noise sounds vintage / broken.tremolomodulates volume,vibratomodulates pitch (via delay). They are easy to confuse; pick the one that matches what "tremolo" means on your instrument.adeclickarordershould be 6–8 for vinyl; higher = more CPU and diminishing returns.threshold2 is a good starting point.adeclipneedsthreshold=10or more for obviously clipped material. Lower thresholds miss hard clips entirely.crystalizerintensity > 3 gets fatiguing fast. Most mastering usesi=1toi=2.apsyclipis the recommended true-peak limiter for broadcast — psychoacoustic masking hides artifacts better than plainalimiter. Setclip=0.9(about -0.9 dBTP) to stay under -1 dBTP.speechnormis better thandynaudnormfor voice-only material (podcasts, audiobooks). For music or mixed content useloudnorminstead.dialoguenhanceoperates on stereo per current docs, but the real-world gain is on 5.1/7.1 where it targets the center channel. On stereo it will run but the effect is subtle.adynamicequalizersupports bothmode=cut(duck a resonance) andmode=boost(lift under a threshold).direction=beloworabovechooses whether it acts on quiet or loud material.biquadis the generic bottom-level IIR. Preferhighpass,lowpass,bandpass,allpass,equalizer,treble,bassunless you genuinely need raw coefficients.afftfilttakes two expressions (realandimag) — advanced spectral domain.re,im,bin_h,mag,phaare available symbols.afreqshiftis frequency shift (single-sideband), not pitch shift. It destroys harmonic relationships. For pitch shift userubberbandorasetrate+atempo.asupercut/asuperpass/asuperstopare high-order Butterworth (up to 20th order) — use when a regularhighpass/bandpassrolloff isn't steep enough.atiltis linear-slope spectral tilt in dB/octave. Useful for pink-noise-style mastering adjustments.- LADSPA requires ffmpeg built with
--enable-ladspa. Plugin.sofiles live in/usr/lib/ladspa(or setLADSPA_PATH). Invoke withfile=cmt(notfile=cmt.so) andplugin=<label>. - LV2 requires ffmpeg built with
--enable-lv2(lilv). Plugins live in/usr/lib/lv2(or setLV2_PATH). Invoke withp=<plugin URI>. Calf's massive library is discoverable withlv2ls | grep -i calf. - After heavy processing always re-dither if going back to integer PCM:
-af "...,aformat=sample_fmts=s16,aresample=44100:dither_method=triangular". - Never chain two strong limiters back-to-back — the second pumps against the first's reduction and creates distortion artifacts.
Examples
Example 1: Podcast voice chain
Input: noisy lavalier recording, interview.wav, needs to land at -16 LUFS for podcast.
ffmpeg -i interview.wav \
-af "asupercut=cutoff=80:order=10,\
speechnorm=e=12.5:r=0.0001:l=1,\
adynamicequalizer=dfrequency=5000:mode=cut:bandwidth=800:makeup=4,\
apsyclip=level_out=0.9" \
-c:a libopus -b:a 96k episode.opus
High-pass kills rumble → speech leveler brings quiet/loud to parity → dynamic EQ tames sibilant 5 kHz resonance → psyclip catches stray peaks.
Example 2: Vinyl restoration
Input: lp_side_a.flac from a USB turntable, clicks and a couple clipped peaks.
ffmpeg -i lp_side_a.flac \
-af "adeclick=arorder=8:threshold=2,\
adeclip=threshold=10:method=a,\
asupercut=cutoff=30:order=12,\
aformat=sample_fmts=s16,aresample=44100:dither_method=triangular" \
restored.flac
Example 3: Shimmer guitar
Chorus + crystalizer for a clean arpeggio:
ffmpeg -i guitar.wav -af "chorus=0.6:0.9:40|60:0.3|0.4:0.2|0.25:1.5|2.0,crystalizer=i=1" shimmer.wav
Example 4: Host a Calf LV2 reverb
ffmpeg -i dry.wav -af "lv2=p=http://calf.sourceforge.net/plugins/Reverb:c=room_size=5|decay_time=3" wet.wav
Troubleshooting
Error: Option chorus not found
Cause: ffmpeg build missing the filter — rare, but Alpine / minimal builds sometimes strip creative filters.
Solution: ffmpeg -filters | grep chorus. If absent, install the full ffmpeg package (Debian/Ubuntu) or rebuild with --enable-filter=chorus.
Error: Error while parsing the string for 'aecho'
Cause: mismatched delay/decay counts — e.g. two delays 1000|1500 with only one decay 0.3.
Solution: counts must match. aecho=0.8:0.9:1000|1500:0.3|0.4.
Error: LADSPA/LV2 No such filter
Cause: ffmpeg was built without --enable-ladspa / --enable-lv2.
Solution: ffmpeg -buildconf | grep -E 'ladspa|lv2'. On macOS via Homebrew: brew install ffmpeg ships with ladspa/lv2 when available; on minimal Docker images you usually must rebuild.
Error: plugin loads but parameters silently ignored
Cause: wrong parameter name for the plugin (LADSPA/LV2 parameter names are plugin-specific).
Solution: listplugins for LADSPA, lv2ls + lv2info <URI> for LV2 — copy the exact control-port name.
Symptom: dialoguenhance has no audible effect
Cause: running on mono or stereo source. The algorithm targets a dialogue-band extraction that is most effective on multichannel content with real separation.
Solution: for stereo, try bandpass=f=2500:w=1200 + a light boost (+2 dB) instead, or upmix first with surround.
Symptom: distortion after limiter
Cause: chaining two strong dynamics stages (e.g. speechnorm l=1 into apsyclip level_in=2) pumps against itself.
Solution: drop limiter input gain to 1.0, or remove one stage. Rule: one make-up-gain stage, one peak-catching stage — never both loud.