a22c238dc4
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
122 lines
3.4 KiB
Metal
122 lines
3.4 KiB
Metal
//
|
|
// MandelbrotShader.metal
|
|
// PsytranceVisualizer
|
|
//
|
|
// Audio-reactive Mandelbrot fractal with zoom and color cycling
|
|
//
|
|
|
|
#include <metal_stdlib>
|
|
using namespace metal;
|
|
|
|
#include "Common.metal"
|
|
|
|
fragment float4 mandelbrotFragment(
|
|
VertexOut in [[stage_in]],
|
|
constant ShaderUniforms& uniforms [[buffer(0)]],
|
|
constant float* fftData [[buffer(1)]],
|
|
constant float* melData [[buffer(2)]],
|
|
constant float* historyData [[buffer(3)]]
|
|
) {
|
|
float2 uv = in.uv;
|
|
float2 resolution = uniforms.resolution;
|
|
float time = uniforms.time;
|
|
float reactivity = uniforms.reactivity;
|
|
|
|
float subBass = uniforms.subBassEnergy;
|
|
float pump = uniforms.sidechainPump;
|
|
float centroid = uniforms.spectralCentroid;
|
|
|
|
// Aspect ratio correction
|
|
float aspectRatio = resolution.x / resolution.y;
|
|
|
|
// Map UV to complex plane
|
|
float2 c = (uv - 0.5) * 2.0;
|
|
c.x *= aspectRatio;
|
|
|
|
// Audio-reactive zoom level
|
|
// Base zoom increases over time, modulated by sub-bass
|
|
float baseZoom = 1.0 + time * 0.02;
|
|
float audioZoom = subBass * 0.5 * (0.5 + reactivity * 0.5);
|
|
float zoom = pow(2.0, baseZoom + audioZoom);
|
|
|
|
// Zoom center - drifts based on sidechain
|
|
float2 zoomCenter = float2(-0.7, 0.0);
|
|
zoomCenter.x += sin(time * 0.1) * 0.3 + pump * 0.1 * sin(time);
|
|
zoomCenter.y += cos(time * 0.13) * 0.2 + pump * 0.1 * cos(time);
|
|
|
|
// Apply zoom
|
|
c = c / zoom + zoomCenter;
|
|
|
|
// Mandelbrot iteration
|
|
float2 z = float2(0.0);
|
|
int maxIterations = int(50.0 + reactivity * 100.0);
|
|
int iterations = 0;
|
|
|
|
float smoothIter = 0.0;
|
|
|
|
for (int i = 0; i < 150; i++) {
|
|
if (i >= maxIterations) break;
|
|
|
|
// z = z^2 + c
|
|
float2 zNew = float2(
|
|
z.x * z.x - z.y * z.y + c.x,
|
|
2.0 * z.x * z.y + c.y
|
|
);
|
|
z = zNew;
|
|
|
|
float mag2 = dot(z, z);
|
|
if (mag2 > 256.0) {
|
|
// Smooth iteration count
|
|
smoothIter = float(i) - log2(log2(mag2)) + 4.0;
|
|
break;
|
|
}
|
|
|
|
iterations = i;
|
|
}
|
|
|
|
// Normalize iteration count
|
|
float normalizedIter = smoothIter / float(maxIterations);
|
|
|
|
// Color based on iterations
|
|
float3 color;
|
|
|
|
if (iterations >= maxIterations - 1) {
|
|
// Inside the set - deep color
|
|
color = deepPurple * (0.5 + 0.5 * subBass);
|
|
} else {
|
|
// Outside - color cycling based on iterations and audio
|
|
float colorPhase = normalizedIter + time * 0.1 + centroid;
|
|
|
|
// Use psytrance palette with color rotation
|
|
color = psytrancePalette(colorPhase, time);
|
|
|
|
// Modulate brightness by iteration depth
|
|
float brightness = 0.5 + 0.5 * sin(smoothIter * 0.3);
|
|
color *= brightness;
|
|
|
|
// Add glow at boundary
|
|
float edgeFactor = 1.0 - normalizedIter;
|
|
edgeFactor = pow(edgeFactor, 3.0);
|
|
color = addGlow(color, edgeFactor * 0.5, neonCyan);
|
|
}
|
|
|
|
// Sub-bass pulse effect
|
|
color *= 0.8 + 0.2 * subBass;
|
|
|
|
// Sidechain breathing
|
|
float breathe = 1.0 + pump * 0.1;
|
|
color *= breathe;
|
|
|
|
// Peak flash in bright areas
|
|
if (uniforms.isPeak > 0.5 && iterations < maxIterations - 1) {
|
|
color += neonMagenta * uniforms.peakIntensity * 0.2 * normalizedIter;
|
|
}
|
|
|
|
// Subtle vignette
|
|
float2 vignetteuv = uv - 0.5;
|
|
float vignette = 1.0 - dot(vignetteuv, vignetteuv) * 0.5;
|
|
color *= vignette;
|
|
|
|
return float4(color, 1.0);
|
|
}
|