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
@@ -0,0 +1,90 @@
//
// AppSettings.swift
// PsytranceVisualizer
//
// Persistent application settings
//
import Foundation
/// Application settings that are persisted between sessions
struct AppSettings: Codable {
/// Selected audio input device UID
var selectedAudioDeviceUID: String?
/// Audio buffer size (512 or 1024 samples)
var bufferSize: Int
/// Last used visualization mode (1-8)
var lastVisualizationMode: Int
/// Reactivity slider value (0.0 - 1.0)
var reactivity: Float
/// Whether app was in fullscreen mode
var isFullscreen: Bool
/// Last window frame (for restoration)
var windowFrame: CodableRect?
/// Volume/gain adjustment
var inputGain: Float
/// Whether to show FPS counter
var showFPS: Bool
/// Default settings
static var `default`: AppSettings {
AppSettings(
selectedAudioDeviceUID: nil,
bufferSize: 1024,
lastVisualizationMode: 1,
reactivity: 0.5,
isFullscreen: false,
windowFrame: nil,
inputGain: 1.0,
showFPS: false
)
}
/// Available buffer sizes
static let availableBufferSizes = [512, 1024]
/// Validates and clamps settings to valid ranges
mutating func validate() {
// Clamp buffer size to valid options
if !AppSettings.availableBufferSizes.contains(bufferSize) {
bufferSize = 1024
}
// Clamp visualization mode
if lastVisualizationMode < 1 || lastVisualizationMode > 8 {
lastVisualizationMode = 1
}
// Clamp reactivity
reactivity = max(0.0, min(1.0, reactivity))
// Clamp input gain
inputGain = max(0.0, min(2.0, inputGain))
}
}
/// Codable wrapper for CGRect
struct CodableRect: Codable {
var x: Double
var y: Double
var width: Double
var height: Double
init(from rect: CGRect) {
self.x = Double(rect.origin.x)
self.y = Double(rect.origin.y)
self.width = Double(rect.size.width)
self.height = Double(rect.size.height)
}
var cgRect: CGRect {
CGRect(x: x, y: y, width: width, height: height)
}
}