Files
Ai/PsytranceVisualizer/Rendering/Shaders/MelSpectrogramShader.metal
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

96 lines
2.7 KiB
Metal

//
// MelSpectrogramShader.metal
// PsytranceVisualizer
//
// Mel spectrogram with scrolling waterfall display
//
#include <metal_stdlib>
using namespace metal;
#include "Common.metal"
fragment float4 melSpectrogramFragment(
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;
float time = uniforms.time;
float reactivity = uniforms.reactivity;
// Configuration
const int numBands = 64;
const int historyLength = 128;
// Map UV to mel band and history position
int bandIndex = int(uv.x * float(numBands));
bandIndex = clamp(bandIndex, 0, numBands - 1);
// Scrolling effect - newer data at bottom
float scrollOffset = fract(time * 0.5); // Scroll speed
float yPos = fract(uv.y + scrollOffset);
// Get mel magnitude
float magnitude = melData[bandIndex];
magnitude = magnitude * (0.5 + reactivity * 1.5);
magnitude = clamp(magnitude, 0.0, 1.0);
// Create waterfall effect using history
int historyIndex = int(yPos * float(historyLength));
historyIndex = clamp(historyIndex, 0, historyLength - 1);
// Combine current and historical data for waterfall
float historicalValue = historyData[historyIndex];
// Blend between current magnitude and position-based intensity
float intensity = magnitude;
// Add some variance based on band position
float bandPhase = float(bandIndex) / float(numBands);
intensity *= 0.8 + 0.2 * sin(bandPhase * 6.28318 + time);
// Apply fade for older data (top of screen)
float ageFade = 1.0 - uv.y * 0.3;
intensity *= ageFade;
// Generate color using heatmap
float3 color = heatmap(intensity);
// Add frequency-dependent hue shift
float hueShift = bandPhase * 0.3;
color = psytrancePalette(intensity + hueShift, time);
// Modulate by actual intensity
color *= 0.3 + intensity * 0.7;
// Add grid lines for visual reference
float gridX = abs(fract(uv.x * float(numBands)) - 0.5) * 2.0;
float gridY = abs(fract(uv.y * 16.0) - 0.5) * 2.0;
float gridLine = smoothstep(0.95, 1.0, gridX) + smoothstep(0.95, 1.0, gridY);
gridLine *= 0.1;
color += float3(gridLine) * uvViolet;
// Add glow on high energy
if (intensity > 0.7) {
float glow = (intensity - 0.7) / 0.3;
color = addGlow(color, glow * 0.5, neonCyan);
}
// Peak flash
if (uniforms.isPeak > 0.5) {
color += neonMagenta * uniforms.peakIntensity * 0.15;
}
// Sub-bass emphasis on lower bands
if (bandIndex < 8) {
color += uvViolet * uniforms.subBassEnergy * 0.3;
}
return float4(color, 1.0);
}