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:
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
Reference in New Issue
Block a user