Personal build. Written for my own use, not maintained for general use.
A macOS music visualizer. It captures the audio output of a selected running app and renders a MilkDrop-style visualization from it using projectM.
Components:
- SwiftUI for the app shell, menus, and Settings window
- libprojectM 4.x, built from source and called from Swift through a C bridging header
- CoreAudio process taps (
AudioHardwareCreateProcessTap, introduced in macOS 14.2) for per-app audio capture
- Audio source is chosen from the Audio menu, listing apps that are currently producing audio; the first one found is selected at launch. Changing it takes effect without a restart.
- Preset navigation: next, previous, random, search-by-name and browse-by-directory (Presets menu), and shuffle over the ~9,800 presets in presets-cream-of-the-crop
- On-screen debug overlay (
D): FPS, current preset name, tapped app, and live audio diagnostics (peak level, ring-buffer backlog/overflow) - Span across displays: one picture spread over all your displays, each window showing its slice. Open a mirror window on each other display (Display > New Mirror Window) and fullscreen it, fullscreen the main window, then turn on Display > Span Across Displays. The layout can follow your System Settings arrangement, each display's picture size and vertical position can be trimmed in Settings, and Display > Span Test Pattern shows a grid to line them up by eye
- Fullscreen (
F, or a double-click on the visualization); it resizes with the window. With the Borderless fullscreen setting (off, mirrors and span windows only, or all windows), windows instead fill the whole screen, notch included. The cursor hides after a configurable idle time in fullscreen (Settings) - Optional scene stream: broadcasts tempo (both audio-beat-derived and, separately, visual-onset-derived, since a preset's on-screen cut rate doesn't always track the song's BPM), dominant color (vibrant/muted/average swatches, HSV), brightness, bass/mid/treble levels, and preset-change events as OSC (Open Sound Control) messages over a local UDP socket, for any OSC-aware process to consume — e.g. Chataigne bridging to music-reactive smart-home lighting, or TouchDesigner
⌘, opens the Settings window, in tabs (General, Displays, Audio & Presets, Scene Stream): mesh quality, render scale (50–200% of the window's pixels), HDR output, borderless mirror fullscreen, fullscreen on launch, cursor auto-hide, beat sensitivity, preset duration, shuffle, and the scene-stream broadcast toggle and destination — all applied live, except HDR output, which needs a restart.
When "Broadcast" (Settings > Scene Stream) is on, projectMac sends OSC (Open Sound Control) messages
over UDP to 127.0.0.1:9000 by default (destination ip:port configurable in Settings, e.g. to reach a device on your network), one address per message, all of a frame's messages in a single OSC bundle, for
any OSC-aware process to consume — e.g. Chataigne bridging to smart-home lighting, or
TouchDesigner. It's deliberately a generic source with no consumer-specific logic:
audio- and visual-derived tempo are broadcast side by side rather than picking a winner,
since a preset's on-screen cut rate doesn't always track the song's BPM.
| Address | Args | Description |
|---|---|---|
/projectmac/tempo/bpm |
f |
Audio energy-onset BPM estimate |
/projectmac/tempo/phase |
f |
0.0-1.0 position within the current audio beat interval |
/projectmac/visual/bpm |
f |
Visual onset detector's rate estimate, same units/shape as tempo/bpm |
/projectmac/visual/phase |
f |
Same, visual-onset-side |
/projectmac/visual/onset |
(bang) | Fired the instant a visual onset is detected |
/projectmac/scene/brightness |
f |
Rec. 709 luma of the sampled color, 0.0-1.0 |
/projectmac/scene/vibrant |
f f f |
Saturation/population-weighted dominant swatch, HSV (hue as a fraction of the circle) |
/projectmac/scene/muted |
f f f |
Second, larger/less-saturated surviving cluster, HSV |
/projectmac/scene/average |
f f f |
Flat-average color, HSV |
/projectmac/audio/bass |
f |
FFT band energy, 0.0-1.0 |
/projectmac/audio/mid |
f |
FFT band energy, 0.0-1.0 |
/projectmac/audio/treble |
f |
FFT band energy, 0.0-1.0 |
/projectmac/preset/changed |
(bang) | Fired on every projectM preset switch |
/projectmac/preset/name |
s |
The new preset's name, sent alongside the bang |
| Key | Action |
|---|---|
N / → |
Next preset |
P / ← |
Previous preset |
R |
Random preset (works regardless of the shuffle setting) |
F |
Toggle fullscreen |
Z |
Toggle span across displays |
D |
Toggle on-screen debug overlay |
Escape |
Leave fullscreen, or close the window if it isn't fullscreen |
Q |
Quit |
These work the same in the main window and in every mirror window. Double-clicking the visualization toggles fullscreen too.
Menu equivalents exist for preset navigation (⌘→ / ⌘← / ⌘R) and audio source selection (Audio menu).
Requirements: macOS 15+, Xcode, Homebrew.
# Build tools
brew install cmake xcodegen glm
# libprojectM 4.x, built from source (see below)
./scripts/deps.sh
# Presets + textures (fetched fresh, not vendored in this repo)
./scripts/fetch-resources.sh
# Generate the Xcode project and build
./scripts/build.sh
open projectMac.xcodeproj./scripts/build.sh takes Debug (default) or Release, plus --unsigned to skip
code signing and --with-deps to run the two setup steps above first. --help lists
everything.
All build scripts live in scripts/, sharing scripts/lib.sh, and can be run from any
directory.
project.yml is the source of truth for the Xcode project, re-run xcodegen generate
after editing it rather than hand-editing projectMac.xcodeproj.
The app links against libprojectM 4.x, which it calls through the library's C API.
Homebrew's projectm package is 3.1.x and exposes a C++ API instead, so the 4.x build
comes from source. ./scripts/deps.sh builds and installs it into /usr/local.
Set PREFIX to install elsewhere: PREFIX="$HOME/.local" ./scripts/deps.sh. Pass the
same PREFIX to scripts/build.sh/scripts/package.sh, which forward it to
xcodebuild as PROJECTM_PREFIX.
vendor/ is gitignored, so the checkout and build tree stay out of this repository.
./scripts/package.sh [version] builds a Release configuration and packages it into
dist/projectMac-<version>.dmg (version defaults to git describe if omitted).
Pass --no-dmg to get dist/projectMac-<version>.app instead. It keeps its code
signature, so it runs straight from dist/.
Pushing a v* tag (e.g. v0.2.0) also runs this via the
Release workflow, attaching the resulting DMG to a
GitHub Release.
Process taps require App Sandbox off and the com.apple.security.device.audio-input
entitlement; both are already configured in project.yml.
Builds are ad-hoc signed by default, which needs no developer account. To sign with your
own Apple Development identity, copy Config/Local.xcconfig.example to
Config/Local.xcconfig and set DEVELOPMENT_TEAM to your team ID. That file is
gitignored, so your team ID stays off the repo.
Audio flows: CoreAudio HAL I/O thread (process tap) to lock-free ring buffer to CVDisplayLink
render thread to projectm_pcm_add_float. The render loop and any preset/settings changes
share a GL context lock, so they do not run concurrently.
When the scene stream is on, the render thread does only the minimal GL-bound framebuffer readback each frame; every reduction downstream of that (dominant-color clustering, onset detection, FFT band split, OSC send) runs on its own serial background queue instead, so it can't add render-thread cost.
- projectM, the visualization engine this app wraps, along with its presets-cream-of-the-crop and presets-milkdrop-texture-pack asset packs.
- frontend-sdl-rust (projectM's Rust/SDL reference frontend, LGPL-licensed), used as a reference for preset/config conventions and keybindings.
- The process-tap code (
Audio/TapResources.swift,Audio/ProcessTapController.swift) was written from Apple's public process-tap API, informed by patterns common to public reference implementations of it, notably pantafive/fader (MIT-licensed).
This project's own code is MIT.
Third-party: libprojectM is LGPL-2.1-or-later, copyright the projectM Team. It is not vendored in this repository; the build steps above clone and build it from its own upstream source, unmodified, and link it statically.


