---
title: "How to add a circular audio visualizer with Claude (tested prompt and script)"
description: "Pink frequency bars fan out around a white ring above the product, shooting out and swelling the ring on every drum hit, then shrinking back between beats. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/circular-audio-visualizer
---

# How to add a circular audio visualizer with Claude

Pink frequency bars fan out around a white ring above the product, shooting out and swelling the ring on every drum hit, then shrinking back between beats.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skills
   ```

3. ### Ask for the effect

   ```
   Add a circular audio visualizer to input.mp4
   Bars around the circle (a multiple of 4): 64
   Circle center, as shares across and down the frame: 0.5, 0.14
   ```

## Several videos, several effects, one ask

Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.

Add a circular audio visualizer to **@clip1.mp4**, put a sale badge on **@clip2.mp4**, give **@clip3.mp4** a VHS look, then join all three with crossfades.

## Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

| Setting           | Default             | What it does                                                               |
| ----------------- | ------------------- | -------------------------------------------------------------------------- |
| `BARS`            | `64`                | Bars around the circle (a multiple of 4).                                  |
| `X, Y`            | `0.5, 0.14`         | Circle center, as shares across and down the frame.                        |
| `RADIUS`          | `0.10`              | Ring radius as a share of the frame width.                                 |
| `BAR_LENGTH`      | `0.10`              | Longest bar as a share of the frame width.                                 |
| `PULSE`           | `0.15`              | How much bigger the ring gets on a loud beat.                              |
| `BAR_COLOR`       | `(255, 60, 170)`    | Bar color.                                                                 |
| `RING_COLOR`      | `(255, 255, 255)`   | Ring color.                                                                |
| `DISC_COLOR`      | `(40, 10, 60, 170)` | See-through dark disc inside the ring; the last number is opacity (0-255). |
| `LOW_HZ, HIGH_HZ` | `40, 8000`          | Frequency range the bars cover.                                            |
| `RELEASE`         | `0.75`              | How slowly bars fall back after a hit (0-1, higher is slower).             |
| `RANGE_DB`        | `50`                | Loudness range from no bar to a full-length bar.                           |
| `RATE`            | `48000`             | Audio sample rate the analysis uses.                                       |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Add a circular audio visualizer to input.mp4 (540x960, 30fps), driven by its own soundtrack. Center a circle at 50% across and 14% down the frame. Fill it with a dark purple disc (40, 10, 60) at about 67% opacity, 92% of the ring's radius, and draw a white (255, 255, 255) ring around it 4 pixels thick. The ring's radius is 10% of the frame width, growing by up to 15% with the bass loudness. Around it draw 64 pink (255, 60, 170) bars pointing straight outward, starting 6 pixels outside the ring, each about 55% as wide as the gap between bars. For each video frame, take a Hann-windowed 2048-sample FFT of the mono audio at 48 kHz centered on that frame, split 40 Hz to 8,000 Hz into 16 log-spaced bands, take each band's peak, convert to dB against the loudest band in the whole clip and map -50 dB..0 dB to 0..1. A bar is 4 pixels plus that value times 10% of the frame width; bars jump up at once and fall to no less than 75% of the previous frame's height. Repeat the 16 bands in each quarter of the circle so bass sits at the left and right and treble at the top and bottom, mirrored. Use Python with numpy and Pillow to draw it and ffmpeg to put it on the video (don't rely on ffmpeg's drawtext filter), keep the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `circular-audio-visualizer.py` and run:

```
python3 circular-audio-visualizer.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Circular audio visualizer: pink bars radiate from a white ring that swells on every beat of the soundtrack.

Usage: python3 circular-audio-visualizer.py input.mp4 output.mp4  (the input needs an audio track)
Needs ffmpeg, numpy and Pillow (pip install numpy pillow).
"""
import json
import math
import subprocess
import sys

import numpy as np
from PIL import Image, ImageDraw

BARS = 64                   # bars around the circle (a multiple of 4)
X, Y = 0.5, 0.14            # circle center (0-1 across and down the frame)
RADIUS = 0.10               # ring radius as a share of the frame width
BAR_LENGTH = 0.10           # longest bar as a share of the frame width
PULSE = 0.15                # how much the ring grows on a loud beat (0.15 = 15% bigger)
BAR_COLOR = (255, 60, 170)  # bar color
RING_COLOR = (255, 255, 255)
DISC_COLOR = (40, 10, 60, 170)  # see-through dark disc inside the ring (last number is opacity, 0-255)
LOW_HZ, HIGH_HZ = 40, 8000  # frequency range the bars cover, bass first
RELEASE = 0.75              # how much of a bar's height carries over to the next frame as the sound dies away
RANGE_DB = 50               # loudness range the bars show, from silent (no bar) to full length
RATE = 48000                # audio sample rate the analysis uses

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate:format=duration", "-of", "json", path]))
    s = info["streams"][0]
    num, den = s["r_frame_rate"].split("/")
    return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den), float(info["format"]["duration"])

def spectrum_per_frame(src, frames):
    """One row of BARS // 4 band heights (0-1) per video frame, from an FFT of the audio around that frame."""
    pcm = subprocess.check_output(["ffmpeg", "-v", "error", "-i", src, "-ac", "1", "-ar", str(RATE), "-f", "f32le", "-"])
    audio = np.frombuffer(pcm, np.float32)
    n = 2048
    window = np.hanning(n)
    freqs = np.fft.rfftfreq(n, 1 / RATE)
    edges = np.geomspace(LOW_HZ, HIGH_HZ, BARS // 4 + 1)
    padded = np.pad(audio, (n // 2, n))
    rows = []
    for i in range(frames):
        start = round(i / fps * RATE)
        mags = np.abs(np.fft.rfft(padded[start:start + n] * window))
        # each band's loudest frequency; the lowest bands are narrower than one FFT bin, so they get at least one
        rows.append([mags[(freqs >= lo) & (freqs < max(hi, lo + freqs[1]))].max() for lo, hi in zip(edges, edges[1:])])
    db = 20 * np.log10(np.array(rows) / max(np.max(rows), 1e-9) + 1e-9)  # 0 dB = loudest moment in the clip
    rows = np.clip(1 + db / RANGE_DB, 0, 1)
    for i in range(1, frames):  # bars jump up at once, then fall back gradually
        rows[i] = np.maximum(rows[i], rows[i - 1] * RELEASE)
    return rows

def overlay(bands):
    # each quarter of the circle shows the spectrum once: bass at the left and right, treble at the top and bottom
    heights = np.concatenate([bands[::-1], bands, bands[::-1], bands])
    img = Image.new("RGBA", (w, h))
    draw = ImageDraw.Draw(img)
    cx, cy = w * X, h * Y
    r = w * RADIUS * (1 + PULSE * bands[:4].mean())  # the bass drives the pulse
    bar_width = max(3, round(2 * math.pi * r / BARS * 0.55))
    draw.ellipse([cx - r * 0.92, cy - r * 0.92, cx + r * 0.92, cy + r * 0.92], fill=DISC_COLOR)
    for k, height in enumerate(heights):
        a = math.pi * 2 * (k + 0.5) / BARS - math.pi / 2
        length = 4 + w * BAR_LENGTH * height
        x0, y0 = cx + math.cos(a) * (r + 6), cy + math.sin(a) * (r + 6)
        x1, y1 = cx + math.cos(a) * (r + 6 + length), cy + math.sin(a) * (r + 6 + length)
        draw.line([x0, y0, x1, y1], fill=BAR_COLOR, width=bar_width)
    draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=RING_COLOR, width=max(3, w // 120))
    return img

def render(src, out):
    """Draw the visualizer on every frame of src and save to out, keeping the audio."""
    frames = math.ceil(duration * fps)
    spectrum = spectrum_per_frame(src, frames)
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-filter_complex", "[0:v][1:v]overlay=shortest=1,format=yuv420p[v]",
                           "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out],
                          stdin=subprocess.PIPE)
    for i in range(frames):
        ff.stdin.write(overlay(spectrum[i]).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
render(src, out)
```

## More music-synced visuals

- [AI voice orb **AI voice orb**](https://www.recharm.com/effects/ai-voice-orb)
- [Audio-reactive blob **Audio-reactive blob**](https://www.recharm.com/effects/audio-reactive-blob)
- [Audio-reactive particles **Audio-reactive particles**](https://www.recharm.com/effects/audio-reactive-particles)
- [Audio-reactive text **Audio-reactive text**](https://www.recharm.com/effects/audio-reactive-text)
- [Audio waveform **Audio waveform**](https://www.recharm.com/effects/audio-waveform)

## Find 3x _winning ads_

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

[Start 14-day free trial](https://www.recharm.com/get-started)
