Add Psytrance Visualizer macOS app with Metal rendering

A complete audio-reactive visualizer for psytrance music featuring:

Audio Analysis (DSPEngine):
- FFT spectrum analysis via Accelerate/vDSP
- 64-band Mel spectrogram
- Sub-bass energy extraction (<100Hz)
- Automatic sidechain pump detection
- Harmonic-to-Noise ratio (HNR) calculation
- Peak/transient detection

8 Visualization Modes (Metal Shaders):
1. FFT Classic - Frequency spectrum bars with glow
2. Mel Spectrogram - Waterfall display
3. Sub-Bass - Pulsating rings
4. Sidechain Pump - Breathing zoom effect
5. Harmonic/Noise - Geometric vs chaotic particles
6. Mandelbrot - Audio-reactive fractal zoom
7. Tunnel Warp - Infinite tunnel with distortion
8. DMT Geometry - Sacred geometry patterns

Features:
- Selectable audio input device (BlackHole support)
- Configurable buffer size (512/1024)
- Reactivity slider for visual intensity
- Auto-hiding control panel
- Fullscreen support with keyboard shortcuts (1-8, F, ESC)
- Persistent settings via UserDefaults
- Psytrance-inspired neon/UV color palette
This commit is contained in:
Claude
2025-12-22 21:36:45 +00:00
parent b607a9cd8a
commit a22c238dc4
29 changed files with 4780 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
//
// AppDelegate.swift
// PsytranceVisualizer
//
// Application delegate handling app lifecycle
//
import AppKit
import AVFoundation
/// Application delegate
final class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: - Properties
private var mainWindowController: MainWindowController?
// MARK: - App Lifecycle
func applicationDidFinishLaunching(_ notification: Notification) {
// Request microphone permission
requestMicrophonePermission()
// Create and show main window
mainWindowController = MainWindowController()
mainWindowController?.showWindow(nil)
mainWindowController?.window?.makeKeyAndOrderFront(nil)
// Activate the application
NSApp.activate(ignoringOtherApps: true)
print("[AppDelegate] Application launched")
}
func applicationWillTerminate(_ notification: Notification) {
// Save settings
SettingsManager.shared.saveNow()
print("[AppDelegate] Application terminating")
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
// MARK: - Permissions
private func requestMicrophonePermission() {
switch AVCaptureDevice.authorizationStatus(for: .audio) {
case .authorized:
print("[AppDelegate] Microphone access already authorized")
case .notDetermined:
AVCaptureDevice.requestAccess(for: .audio) { granted in
if granted {
print("[AppDelegate] Microphone access granted")
} else {
print("[AppDelegate] Microphone access denied")
self.showMicrophonePermissionAlert()
}
}
case .denied, .restricted:
print("[AppDelegate] Microphone access denied or restricted")
showMicrophonePermissionAlert()
@unknown default:
break
}
}
private func showMicrophonePermissionAlert() {
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = "Microphone Access Required"
alert.informativeText = "Psytrance Visualizer needs access to your audio input to visualize music. Please enable microphone access in System Preferences > Security & Privacy > Privacy > Microphone."
alert.alertStyle = .warning
alert.addButton(withTitle: "Open System Preferences")
alert.addButton(withTitle: "Cancel")
if alert.runModal() == .alertFirstButtonReturn {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone") {
NSWorkspace.shared.open(url)
}
}
}
}
// MARK: - Menu Actions
@IBAction func showAbout(_ sender: Any) {
let alert = NSAlert()
alert.messageText = "Psytrance Visualizer"
alert.informativeText = """
An audio-reactive visualizer for psytrance music.
8 Visualization Modes:
1 - FFT Classic
2 - Mel Spectrogram
3 - Sub-Bass
4 - Sidechain Pump
5 - Harmonic/Noise
6 - Mandelbrot
7 - Tunnel Warp
8 - DMT Geometry
Keyboard Shortcuts:
1-8: Switch visualization mode
F: Toggle fullscreen
ESC: Exit fullscreen
Tip: Use a virtual audio device like BlackHole to route system audio.
"""
alert.alertStyle = .informational
alert.runModal()
}
}
@@ -0,0 +1,133 @@
//
// PsytranceVisualizerApp.swift
// PsytranceVisualizer
//
// Main application entry point
//
import AppKit
// MARK: - Main Entry Point
/// Application entry point
@main
struct PsytranceVisualizerApp {
static func main() {
// Create the application
let app = NSApplication.shared
// Set up the delegate
let delegate = AppDelegate()
app.delegate = delegate
// Set activation policy
app.setActivationPolicy(.regular)
// Create the main menu
setupMainMenu()
// Run the application
app.run()
}
/// Sets up the application's main menu
private static func setupMainMenu() {
let mainMenu = NSMenu()
// Application menu
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenuItem.submenu = appMenu
appMenu.addItem(withTitle: "About Psytrance Visualizer",
action: #selector(AppDelegate.showAbout(_:)),
keyEquivalent: "")
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Hide Psytrance Visualizer",
action: #selector(NSApplication.hide(_:)),
keyEquivalent: "h")
let hideOthersItem = appMenu.addItem(withTitle: "Hide Others",
action: #selector(NSApplication.hideOtherApplications(_:)),
keyEquivalent: "h")
hideOthersItem.keyEquivalentModifierMask = [.command, .option]
appMenu.addItem(withTitle: "Show All",
action: #selector(NSApplication.unhideAllApplications(_:)),
keyEquivalent: "")
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Quit Psytrance Visualizer",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q")
// View menu
let viewMenuItem = NSMenuItem()
mainMenu.addItem(viewMenuItem)
let viewMenu = NSMenu(title: "View")
viewMenuItem.submenu = viewMenu
viewMenu.addItem(withTitle: "Toggle Fullscreen",
action: #selector(NSWindow.toggleFullScreen(_:)),
keyEquivalent: "f")
viewMenu.addItem(NSMenuItem.separator())
// Visualization mode submenu
let modesMenuItem = NSMenuItem(title: "Visualization Mode", action: nil, keyEquivalent: "")
let modesMenu = NSMenu()
for mode in VisualizationMode.allCases {
let item = NSMenuItem(title: mode.displayName,
action: nil,
keyEquivalent: mode.shortcut)
item.tag = mode.rawValue
modesMenu.addItem(item)
}
modesMenuItem.submenu = modesMenu
viewMenu.addItem(modesMenuItem)
// Window menu
let windowMenuItem = NSMenuItem()
mainMenu.addItem(windowMenuItem)
let windowMenu = NSMenu(title: "Window")
windowMenuItem.submenu = windowMenu
windowMenu.addItem(withTitle: "Minimize",
action: #selector(NSWindow.miniaturize(_:)),
keyEquivalent: "m")
windowMenu.addItem(withTitle: "Zoom",
action: #selector(NSWindow.zoom(_:)),
keyEquivalent: "")
windowMenu.addItem(NSMenuItem.separator())
windowMenu.addItem(withTitle: "Bring All to Front",
action: #selector(NSApplication.arrangeInFront(_:)),
keyEquivalent: "")
// Help menu
let helpMenuItem = NSMenuItem()
mainMenu.addItem(helpMenuItem)
let helpMenu = NSMenu(title: "Help")
helpMenuItem.submenu = helpMenu
helpMenu.addItem(withTitle: "Psytrance Visualizer Help",
action: #selector(AppDelegate.showAbout(_:)),
keyEquivalent: "?")
NSApp.mainMenu = mainMenu
NSApp.windowsMenu = windowMenu
NSApp.helpMenu = helpMenu
}
}