โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ต WINDOW DANCE PLAYER ๐ต โ
โ Inspired by Rhythm Doctor's Window Dance โ
โ ๐ Now with YARIS MODE ๐ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
yarismode.mp4
A terminal music player that moves and resizes your terminal window in sync with the beat of whatever you're playing. Six choreographed patterns plus a physics-based Toyota Yaris lowrider bounce (YARIS MODE). Supports Hyprland, Sway, KDE Plasma Wayland, and X11 out of the box.
Inspired by the Window Dance mechanic in Rhythm Doctor.
- Beat-accurate window animation โ librosa's beat tracker feeds a sub-millisecond polling loop; lead-time compensation keeps the visual effect aligned with the audio beat even under compositor latency
- Six dance patterns โ Circle, Corner Bounce, Zigzag, Figure-8, Random Jump, and the physics-based ๐ YARIS MODE
- YARIS MODE physics engine โ underdamped spring simulation with gravity, launch velocity, landing squash, and horizontal wobble; runs at display refresh rate (up to 360 Hz)
- Zero-subprocess hot path โ Hyprland IPC via raw Unix socket, Sway via i3 binary protocol, X11 via libX11 ctypes; no process fork per frame
- Auto-pattern rotation โ cycles through non-Yaris patterns every 32 beats so the dance stays interesting on long tracks
- Curses TUI โ live beat visualiser, progress bar, playlist, BPM display, and beat flash indicator
- Playlist support โ pass any number of audio files; auto-advances at track end
| Distro | Compositor | Status |
|---|---|---|
| CachyOS (Arch-based) | Hyprland | โ Fully working โ primary dev environment |
| Distro / OS | Compositor | Notes |
|---|---|---|
| Arch Linux | Hyprland | Same as CachyOS |
| Arch Linux | Sway | Via i3 IPC socket |
| Arch Linux | KDE Plasma 6 Wayland | Via KWin scripting (slower, 4 fps) |
| Arch Linux / any | X11 (any WM) | Requires xdotool; libX11 ctypes used as fast path |
| Manjaro | Hyprland / X11 | Should work identically to Arch |
| EndeavourOS | Hyprland / X11 | Should work identically to Arch |
| Ubuntu 22.04+ | X11 / Sway | xdotool from apt; swaymsg bundled with Sway |
| Fedora 38+ | X11 / Sway / KDE | xdotool from dnf |
| openSUSE Tumbleweed | X11 / KDE | xdotool from zypper |
| macOS | โ | Window move only (no resize); uses osascript |
| Windows 10/11 | โ | Basic move via user32.SetWindowPos |
| Compositor | Backend | FPS cap | Notes |
|---|---|---|---|
| Hyprland | Direct Unix IPC [[BATCH]] |
Display Hz | Best experience; hyprctl for queries |
| Sway | i3-protocol Unix socket | Display Hz | Window must be floating |
| KDE Plasma Wayland | KWin JS scripting via qdbus | ~4 fps | Requires qdbus6 or qdbus |
| X11 (any) | libX11 ctypes โ xdotool fallback | Display Hz | xdotool only needed if libX11 unavailable |
| GNOME Wayland | โ Not supported | โ | No standard API for window control |
Tiling window managers lock window geometry. Before running, make your terminal window float:
| Compositor | How to float |
|---|---|
| Hyprland | Super+V (or windowrule float in config) |
| Sway | $mod+Shift+Space |
| KDE Plasma | Right-click titlebar โ More Actions โ Float |
librosa >= 0.10.0
pygame >= 2.5.0
numpy >= 1.24.0
| Tool | Used for | How to install |
|---|---|---|
xdotool |
X11 window control (fallback) | sudo pacman -S xdotool / sudo apt install xdotool |
hyprctl |
Hyprland window queries | Bundled with Hyprland |
swaymsg |
Sway window control | Bundled with Sway |
qdbus6 / qdbus |
KDE KWin scripting | Pre-installed with KDE Plasma |
xrandr |
Screen-size detection on X11 | Usually pre-installed |
# 1. Clone
git clone https://github.com/Ymsniper/window-dance-player.git
cd window-dance-player
# 2. System packages (pygame + numpy are faster from pacman)
sudo pacman -S python-pygame python-numpy
# 3. librosa (not in pacman, install via pip)
pip install librosa --break-system-packages
# 4. X11 users only
sudo pacman -S xdotoolgit clone https://github.com/Ymsniper/window-dance-player.git
cd window-dance-player
sudo apt install python3-pip xdotool
pip install -r requirements.txt --break-system-packagesgit clone https://github.com/Ymsniper/window-dance-player.git
cd window-dance-player
sudo dnf install xdotool
pip install -r requirements.txt --break-system-packagesgit clone https://github.com/Ymsniper/window-dance-player.git
cd window-dance-player
pip install . --break-system-packagesAfter pip install, the window-dance-player command is available globally.
# Single file
python -m window_dance_player song.mp3
# Playlist (auto-advances)
python -m window_dance_player *.mp3
# Or, after pip install:
window-dance-player song.mp3 song2.flac song3.wav| Key | Action |
|---|---|
SPACE |
Play / Pause |
N |
Next track |
P |
Previous track |
D |
Toggle window dance on/off |
1 |
Pattern: Circle |
2 |
Pattern: Corner Bounce |
3 |
Pattern: Zigzag |
4 |
Pattern: Figure-8 |
5 |
Pattern: Random Jump |
6 |
๐ YARIS MODE |
Q |
Quit |
Pattern 6 is a physics simulation of the Toyota Yaris lowrider bounce meme.
On every detected beat:
- The window snaps to the floor (base Y position)
- An instantaneous squash is applied (
scale_y = 0.64, window goes wide and flat) - An underdamped spring kick (
vel_scale_y = 3.0) causes the spring to overshoot pastscale_y = 1.0all the way into stretch territory (scale_y > 1.0) - Simultaneously, gravity launches the window upward (
vel_y = -380 px/s) - The window arcs upward, stretching as it rises
- Gravity pulls it back, it lands, squashes proportional to impact speed, repeats
The key physics constraint is that the scale spring must be underdamped:
SCALE_DAMP < 2 ร sqrt(SCALE_SPRING) โ 5 < 2 ร sqrt(80) โ 17.9 โ
If overdamped, the spring only slowly returns to 1.0 โ no overshoot, no stretch, no meme. The damping ratio ฮถ โ 0.28 gives a satisfying bouncy oscillation.
The project is split into focused modules for easy auditing:
window_dance_player/
โโโ __init__.py Version metadata
โโโ __main__.py Enables python -m window_dance_player
โโโ logger.py Centralised DBG/WARN/ERR โ wdp_debug.log
โโโ main.py Argument parsing, startup checks, curses launch
โโโ player.py Audio playback + beat-watcher thread
โโโ ui.py Curses TUI (~25 FPS render loop)
โ
โโโ platform/
โ โโโ detect.py PLATFORM / SESSION_TYPE / COMPOSITOR constants
โ โโโ x11.py libX11 ctypes fast path (_init_x11_fast)
โ โโโ hyprland.py Hyprland Unix IPC (_hyprland_batch, _hyprland_query)
โ โโโ sway.py Sway i3-protocol socket (_sway_command)
โ โโโ kde.py KDE KWin scripting via qdbus (_kwin_script)
โ โโโ window.py Dispatch: get_window_id / get_screen_size / move_window
โ
โโโ audio/
โ โโโ analysis.py analyze_audio() โ (duration, bpm, beat_times[])
โ
โโโ dance/
โโโ patterns.py PATTERNS list + PATTERN_LABELS dict
โโโ physics.py YarisPhysics class + _sim_yaris_frames()
โโโ dancer.py WindowDancer: on_beat(), _physics_loop(), patterns
Audio file
โโโบ librosa.load() (background thread, mono, native SR)
โโโบ librosa.beat.beat_track() โ tempo (BPM) + beat_frames[]
โโโบ librosa.frames_to_time() โ beat_times[] in seconds
Beat watcher thread (2 ms poll):
pos = time.now() - start_time
while beat_times[i] <= pos + LEAD_TIME:
dancer.on_beat()
i++
The LEAD_TIME constant compensates for IPC latency:
- KDE: 175 ms (KWin scripting round-trip)
- X11: 45 ms (xdotool fork + X compositing)
- Hyprland/Sway: 25 ms (fast socket + one Wayland frame)
The hot path in WindowDancer._physics_loop() pre-resolves all
compositor handles before entering the loop so there is zero
conditional overhead or module attribute lookup per frame:
# Pre-cache once:
_hsock = _hypr._HYPR_SOCK
_htmpl = "[[BATCH]]dispatch resizewindowpixel exact {w} {h},address:..."
# Hot loop (runs at 60โ360 Hz):
payload = _htmpl.format(w=cw, h=ch, x=x, y=y).encode()
with socket(AF_UNIX) as s:
s.connect(_hsock)
s.sendall(payload) # one connect + one send per frame| Thread | Purpose | Runs at |
|---|---|---|
| Main | pygame audio + curses UI | ~25 FPS |
_analyze |
librosa beat detection | Once per track |
_watch |
Beat timing poll โ on_beat() |
2 ms intervals |
_physics_loop |
Yaris physics โ IPC | Display Hz (60โ360) |
A debug log is written to wdp_debug.log in the working directory on
every run. It contains:
- Startup platform/compositor detection results
- IPC socket paths resolved
- Every beat fired (first 5, then every 20th)
- Physics loop first-frame diagnostics
- Any IPC errors
tail -f wdp_debug.log # live debug output while running- Fork the repo
- Create a branch:
git checkout -b feature/my-feature - Make your changes with clear, focused commits
- Open a pull request
The module split is intentional โ each file in platform/ is a
self-contained backend. Adding a new compositor means adding one file
there and a branch in platform/window.py.
Copyright (C) 2024 Ymsniper
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
See LICENSE for the full text.