Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

projectm-setup

MilkDrop visualisation on Hyprland, wired to whatever is currently playing.

It captures audio from the PipeWire monitor source of the default sink, so it visualises any application — a music player, a browser tab, anything. Bound to SUPER+M as a toggle.

Built around projectm-pulseaudio 3.1.12 on Arch/CachyOS. Most of this repo exists to work around undocumented behaviour in that version; see Findings.

Install

git clone https://github.com/maribotto/projectm-setup ~/projectm-setup
cd ~/projectm-setup
./install.sh

install.sh symlinks bin/ into ~/.local/bin and copies config/ into ~/.config/projectM, rewriting the absolute paths for your home directory. Existing configs are backed up first.

Scripts are symlinked, not copied, so this directory stays the single source of truth — edit here and the change takes effect immediately.

Hyprland settings are not installed automatically; they belong in your own config. See hypr/snippets.lua.

Requirements

projectm-pulseaudio the visualiser itself (pacman -S projectm-pulseaudio)
PipeWire or PulseAudio pactl is used to find the monitor source
Hyprland optional; only for the window rules and the idle-preset workaround

Layout

bin/projectm-vis launch/close, audio source, first preset
bin/projectm-playlist builds the .prjm playlist from the preset tree
config/config.inp projectM's own settings (mesh, fps, duration)
config/qprojectM.conf Qt frontend — the playlist path lives here
config/qprojectM-pulseaudio.conf audio device
hypr/snippets.lua window rules and keybinding

Presets

Presets live in ~/.local/share/projectM/presets/:

presets/
├── system -> /usr/share/projectM/presets   4188  (managed by the package manager)
├── milkdrop-original/                       552  (git)
└── en-d/                                     40  (git)

Add more from the projectM organisation:

cd ~/.local/share/projectM/presets
git clone --depth 1 https://github.com/projectM-visualizer/presets-en-d en-d
git clone --depth 1 https://github.com/projectM-visualizer/presets-milkdrop-original milkdrop-original

Nesting depth does not matter — the playlist generator walks the whole tree. The playlist is rebuilt on every launch, so changes take effect by themselves.

presets-cream-of-the-crop (9795 presets) is deliberately not included. Its MilkDrop 2 presets lean heavily on HLSL shaders that projectM 3.1.12 only partially supports, so many render wrong or black — and on its own it exceeds the crash threshold described below.

Textures

~/.local/share/projectM/textures/ holds 67 images from presets-milkdrop-texture-pack.

projectM looks for textures in a hardcoded path, /usr/share/projectM/textures. config.inp has no key for a texture path at all, so a symlink is the only option. Run once:

sudo ln -s ~/.local/share/projectM/textures /usr/share/projectM/textures

Careful: if that path already exists, ln -s follows it and creates the link inside the target, producing a symlink loop that projectM's FileScanner will follow (it uses FTS_LOGICAL). Check with ls -la /usr/share/projectM/.

A symlink is the right shape here because the package manager does not own it and so will not remove it on upgrade.

Findings

None of the following is documented upstream. Each was established by reading the projectM 3.1.12 source or by measurement.

Preset Path does nothing

config.inp's Preset Path is overridden. The Qt frontend stores its own path in PlaylistFile in qprojectM.conf, and that wins. The file is created on first run and freezes whatever path was current at that moment.

The directory scan is not recursive

The Qt frontend reads only the top level of the directory it is given. Point it at a tree of preset packs and the playlist comes up empty, with the built-in idle preset on screen.

This is why bin/projectm-playlist exists: it walks the whole tree, following symlinks, and writes a .prjm playlist that PlaylistFile points at.

A consequence worth knowing: /usr/share/projectM/presets has 6 native .so presets in its root and zero .milk files. On a default install those six are the only presets in rotation — the package's 4188 MilkDrop presets are never loaded.

Roughly 5000 presets is the ceiling

projectM exits silently at startup, with no error message, if the playlist is too long. Bisected: 5000 entries load fine, 8000 crash.

projectm-playlist warns when the count goes over 5000.

The idle preset holds the screen for 30 seconds

At startup projectM loads a built-in idle preset — idle://Geiss & Sperl - Feedback (projectM idle HDR mix).milk, the M logo with headphones. It is not on the playlist, and Shuffle Enabled has no effect on it, because shuffle only governs transitions.

It is replaced only once Preset Duration elapses, 30 seconds by default.

projectm-vis works around this by sending CTRL-R (random preset) as soon as the window appears, via Hyprland's send_shortcut dispatcher. Without Hyprland, press CTRL-R yourself.

Successful texture loading is silent

TextureManager's constructor scans datadir + "/presets", datadir + "/textures" and the preset path at startup, and loads everything it finds into memory without logging anything.

The Located texture / Failed to locate texture lines come from a fallback path used only for names the scan did not find. Silence means the textures were found.

(projectM 3.1.12 has a bug here: Failed to locate texture is printed after a successful load too, because the break is not followed by a return. Do not read that log line literally.)

Process handling traps

All three are worked around in projectm-vis:

  • The process name is truncated to 15 characters (projectM-pulsea), so pkill -x projectM-pulseaudio never matches. Match the command line instead.
  • projectM ignores SIGTERM. Measured: the process was still alive 134 s after timeout 12 sent one. Closing it requires SIGKILL.
  • pkill -f projectM-pulseaudio kills your own shell if that string appears on its command line. Do not run it interactively by hand.

Settings worth tuning

In config/config.inp:

key value note
Mesh X / Mesh Y 220 / 125 resolution of the per-pixel equation mesh. Heavy at 4K; 128/72 is about a third of the load
FPS 60 appears to do nothing — see below
Preset Duration 30 seconds per preset
Shuffle Enabled true already the built-in default; present for visibility
Preset Path unused, see Findings

On FPS: at 5 the CPU load measured 23–25%, at 60 it measured 17–21% — the wrong direction. FPS is a timing parameter inside projectM, not a frame limiter.

Keyboard shortcuts

projectM's own, while the window has focus:

CTRL-R random preset
CTRL-N / CTRL-P next / previous
CTRL-Y toggle shuffle
CTRL-L lock the current preset
CTRL-F fullscreen
CTRL-M show / hide the menu

Tested on

CachyOS, Hyprland 0.56.1, Wayland, PipeWire 1.6.8, projectm-pulseaudio 3.1.12, Intel Arc Pro 130T/140T, 3840x2160 @ 60 Hz at scale 2.

libprojectm 4.1.6 is installed on that system but unused — there is no packaged frontend for the 4.x series, and the 3.x frontends link against libprojectM.so.3.

License

MIT, see LICENSE.

Presets and textures are not part of this repository; they carry their own licenses from the projectM organisation.

About

projectM MilkDrop setup for Hyprland

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages