Skip to content

About

A native macOS music visualizer built with libprojectM and SwiftUI

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

projectM(ac)

Build

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

Screenshots

Windowed, with the debug overlay showing FPS, preset name, and the tapped app

A different preset

Fullscreen with the Settings window open

Features

  • 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

Settings

⌘, 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.

Scene stream (OSC)

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

Keyboard shortcuts

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).

Building

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.

libprojectM

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.

Packaging

./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.

Signing & entitlements

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.

Architecture

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.

Acknowledgments

  • 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).

License

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.

About

A native macOS music visualizer built with libprojectM and SwiftUI

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages