Files
Claude a22c238dc4 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
2025-12-22 21:36:45 +00:00

242 lines
6.2 KiB
Metal

//
// Common.metal
// PsytranceVisualizer
//
// Shared shader functions, types, and psytrance color palette
//
#include <metal_stdlib>
using namespace metal;
// MARK: - Uniforms Structure
struct ShaderUniforms {
float time;
float2 resolution;
float reactivity;
float subBassEnergy;
float sidechainPump;
float sidechainEnvelope;
float hnrRatio;
float isPeak;
float peakIntensity;
float spectralCentroid;
float rmsLevel;
int mode;
float2 padding;
};
// MARK: - Vertex Data
struct VertexOut {
float4 position [[position]];
float2 uv;
};
// MARK: - Psytrance Color Palette
constant float3 neonMagenta = float3(1.0, 0.0, 1.0);
constant float3 neonCyan = float3(0.0, 1.0, 1.0);
constant float3 neonGreen = float3(0.224, 1.0, 0.078);
constant float3 uvViolet = float3(0.482, 0.0, 1.0);
constant float3 hotPink = float3(1.0, 0.2, 0.6);
constant float3 electricBlue = float3(0.0, 0.5, 1.0);
constant float3 deepPurple = float3(0.1, 0.0, 0.15);
// MARK: - Palette Functions
inline float3 getPaletteColor(int index) {
switch (index % 6) {
case 0: return neonMagenta;
case 1: return neonCyan;
case 2: return neonGreen;
case 3: return uvViolet;
case 4: return hotPink;
default: return electricBlue;
}
}
inline float3 rainbowPalette(float t) {
float3 a = float3(0.5, 0.5, 0.5);
float3 b = float3(0.5, 0.5, 0.5);
float3 c = float3(1.0, 1.0, 1.0);
float3 d = float3(0.0, 0.33, 0.67);
return a + b * cos(6.28318 * (c * t + d));
}
inline float3 psytrancePalette(float t, float time) {
// Cycle through psytrance colors
float phase = fract(t + time * 0.1);
if (phase < 0.2) {
return mix(uvViolet, neonMagenta, phase * 5.0);
} else if (phase < 0.4) {
return mix(neonMagenta, hotPink, (phase - 0.2) * 5.0);
} else if (phase < 0.6) {
return mix(hotPink, neonCyan, (phase - 0.4) * 5.0);
} else if (phase < 0.8) {
return mix(neonCyan, neonGreen, (phase - 0.6) * 5.0);
} else {
return mix(neonGreen, uvViolet, (phase - 0.8) * 5.0);
}
}
// MARK: - Heatmap for Spectrogram
inline float3 heatmap(float t) {
// Low energy: dark purple
// High energy: white through neon colors
if (t < 0.2) {
return mix(float3(0.05, 0.0, 0.1), uvViolet, t * 5.0);
} else if (t < 0.4) {
return mix(uvViolet, neonMagenta, (t - 0.2) * 5.0);
} else if (t < 0.6) {
return mix(neonMagenta, hotPink, (t - 0.4) * 5.0);
} else if (t < 0.8) {
return mix(hotPink, neonCyan, (t - 0.6) * 5.0);
} else {
return mix(neonCyan, float3(1.0), (t - 0.8) * 5.0);
}
}
// MARK: - Noise Functions
// Simplex-like noise
inline float hash(float2 p) {
float3 p3 = fract(float3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
inline float noise(float2 p) {
float2 i = floor(p);
float2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + float2(1.0, 0.0));
float c = hash(i + float2(0.0, 1.0));
float d = hash(i + float2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
inline float fbm(float2 p, int octaves) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < octaves; i++) {
value += amplitude * noise(p * frequency);
frequency *= 2.0;
amplitude *= 0.5;
}
return value;
}
// 3D noise for volumetric effects
inline float noise3D(float3 p) {
float3 i = floor(p);
float3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float2 uv = i.xy + float2(37.0, 17.0) * i.z;
float a = hash(uv);
float b = hash(uv + float2(1.0, 0.0));
float c = hash(uv + float2(0.0, 1.0));
float d = hash(uv + float2(1.0, 1.0));
float2 uv2 = uv + float2(37.0, 17.0);
float e = hash(uv2);
float ff = hash(uv2 + float2(1.0, 0.0));
float g = hash(uv2 + float2(0.0, 1.0));
float h = hash(uv2 + float2(1.0, 1.0));
float x1 = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
float x2 = mix(mix(e, ff, f.x), mix(g, h, f.x), f.y);
return mix(x1, x2, f.z);
}
// MARK: - Utility Functions
inline float2 rotate(float2 p, float angle) {
float c = cos(angle);
float s = sin(angle);
return float2(p.x * c - p.y * s, p.x * s + p.y * c);
}
inline float map(float value, float inMin, float inMax, float outMin, float outMax) {
return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
}
inline float smoothstepEdge(float edge0, float edge1, float x) {
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
// MARK: - Glow Effect
inline float3 addGlow(float3 color, float intensity, float3 glowColor) {
return color + glowColor * intensity * intensity;
}
// MARK: - SDF Functions for Geometry
inline float sdCircle(float2 p, float r) {
return length(p) - r;
}
inline float sdBox(float2 p, float2 b) {
float2 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
inline float sdHexagon(float2 p, float r) {
const float3 k = float3(-0.866025404, 0.5, 0.577350269);
p = abs(p);
p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
p -= float2(clamp(p.x, -k.z * r, k.z * r), r);
return length(p) * sign(p.y);
}
inline float sdStar(float2 p, float r, int n, float m) {
float an = 3.141593 / float(n);
float en = 3.141593 / m;
float2 acs = float2(cos(an), sin(an));
float2 ecs = float2(cos(en), sin(en));
float bn = fmod(atan2(p.x, p.y), 2.0 * an) - an;
p = length(p) * float2(cos(bn), abs(sin(bn)));
p -= r * acs;
p += ecs * clamp(-dot(p, ecs), 0.0, r * acs.y / ecs.y);
return length(p) * sign(p.x);
}
// MARK: - Vertex Shader (Fullscreen Quad)
vertex VertexOut vertexShader(uint vertexID [[vertex_id]]) {
// Generate fullscreen quad
float2 positions[4] = {
float2(-1.0, -1.0),
float2( 1.0, -1.0),
float2(-1.0, 1.0),
float2( 1.0, 1.0)
};
float2 uvs[4] = {
float2(0.0, 1.0),
float2(1.0, 1.0),
float2(0.0, 0.0),
float2(1.0, 0.0)
};
VertexOut out;
out.position = float4(positions[vertexID], 0.0, 1.0);
out.uv = uvs[vertexID];
return out;
}