https://claude.ai/public/artifacts/2366f48b-b3de-4d5a-a05a-b432397df0a9,import React, { useState, useEffect, useRef } from 'react';
import { Play, Pause, Volume2, Palette, Sparkles, Waves } from 'lucide-react';
const MusicVisualizer = () => {
const [mode, setMode] = useState('particles');
const [isPlaying, setIsPlaying] = useState(false);
const [color, setColor] = useState('#00ffff');
const [intensity, setIntensity] = useState(50);
const canvasRef = useRef(null);
const animationRef = useRef(null);
const particlesRef = useRef([]);
const timeRef = useRef(0);
const audioContextRef = useRef(null);
const analyserRef = useRef(null);
const dataArrayRef = useRef(null);
const modes = [
{ id: 'particles', name: 'Particle Storm', icon: Sparkles },
{ id: 'waveform', name: 'Waveform', icon: Waves },
{ id: 'spectrum', name: 'Spectrum Bars', icon: Volume2 },
{ id: 'kaleidoscope', name: 'Kaleidoscope', icon: Palette }
];
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// Initialize particles
for (let i = 0; i < 100; i++) {
particlesRef.current.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
size: Math.random() * 3 + 1,
hue: Math.random() * 360
});
}
const handleResize = () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const initAudio = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
analyserRef.current = audioContextRef.current.createAnalyser();
analyserRef.current.fftSize = 256;
const source = audioContextRef.current.createMediaStreamSource(stream);
source.connect(analyserRef.current);
const bufferLength = analyserRef.current.frequencyBinCount;
dataArrayRef.current = new Uint8Array(bufferLength);
} catch (err) {
console.log('Audio input not available, using simulated data');
}
};
const getAudioData = () => {
if (analyserRef.current && dataArrayRef.current) {
analyserRef.current.getByteFrequencyData(dataArrayRef.current);
return dataArrayRef.current;
}
// Simulated audio data
const simulated = new Uint8Array(128);
for (let i = 0; i < simulated.length; i++) {
simulated[i] = Math.sin(timeRef.current * 0.01 + i * 0.1) * 128 + 128;
}
return simulated;
};
const drawParticles = (ctx, audioData) => {
const canvas = ctx.canvas;
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const avgFreq = audioData.reduce((a, b) => a + b) / audioData.length;
const scale = (avgFreq / 128) * (intensity / 50);
particlesRef.current.forEach((p, i) => {
const freq = audioData[i % audioData.length] / 255;
p.x += p.vx * (1 + freq * scale);
p.y += p.vy * (1 + freq * scale);
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
p.x = Math.max(0, Math.min(canvas.width, p.x));
p.y = Math.max(0, Math.min(canvas.height, p.y));
const hue = (p.hue + freq * 50) % 360;
ctx.fillStyle = `hsla(${hue}, 100%, 50%, ${0.8 * freq})`;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * (1 + freq * 2), 0, Math.PI * 2);
ctx.fill();
// Draw connections
particlesRef.current.slice(i + 1, i + 4).forEach(p2 => {
const dist = Math.hypot(p.x - p2.x, p.y - p2.y);
if (dist < 100) {
ctx.strokeStyle = `hsla(${hue}, 100%, 50%, ${0.2 * (1 - dist / 100)})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
});
});
};
const drawWaveform = (ctx, audioData) => {
const canvas = ctx.canvas;
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const sliceWidth = canvas.width / audioData.length;
for (let pass = 0; pass < 3; pass++) {
ctx.beginPath();
ctx.lineWidth = 3;
for (let i = 0; i < audioData.length; i++) {
const x = i * sliceWidth;
const v = audioData[i] / 255;
const y = canvas.height / 2 + (v - 0.5) * canvas.height * (intensity / 50) * Math.sin(timeRef.current * 0.001 + pass);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
const hue = (timeRef.current * 0.5 + pass * 120) % 360;
ctx.strokeStyle = `hsla(${hue}, 100%, 50%, 0.6)`;
ctx.stroke();
}
};
const drawSpectrum = (ctx, audioData) => {
const canvas = ctx.canvas;
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = canvas.width / audioData.length;
audioData.forEach((value, i) => {
const height = (value / 255) * canvas.height * (intensity / 50);
const x = i * barWidth;
const hue = (i / audioData.length) * 360;
const gradient = ctx.createLinearGradient(x, canvas.height - height, x, canvas.height);
gradient.addColorStop(0, `hsla(${hue}, 100%, 50%, 0.8)`);
gradient.addColorStop(1, `hsla(${(hue + 60) % 360}, 100%, 30%, 0.8)`);
ctx.fillStyle = gradient;
ctx.fillRect(x, canvas.height - height, barWidth - 2, height);
// Reflection
ctx.fillStyle = `hsla(${hue}, 100%, 50%, 0.2)`;
ctx.fillRect(x, canvas.height, barWidth - 2, height * 0.3);
});
};
const drawKaleidoscope = (ctx, audioData) => {
const canvas = ctx.canvas;
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const segments = 8;
ctx.save();
ctx.translate(centerX, centerY);
for (let seg = 0; seg < segments; seg++) {
ctx.save();
ctx.rotate((Math.PI * 2 * seg) / segments);
audioData.forEach((value, i) => {
const radius = (i / audioData.length) * Math.min(centerX, centerY) * (intensity / 50);
const size = (value / 255) * 20;
const angle = (i / audioData.length) * Math.PI * 2 + timeRef.current * 0.001;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
const hue = (timeRef.current * 0.5 + i) % 360;
ctx.fillStyle = `hsla(${hue}, 100%, 50%, 0.6)`;
ctx.fillRect(x - size / 2, y - size / 2, size, size);
});
ctx.restore();
}
ctx.restore();
};
const animate = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const audioData = getAudioData();
timeRef.current++;
switch (mode) {
case 'particles':
drawParticles(ctx, audioData);
break;
case 'waveform':
drawWaveform(ctx, audioData);
break;
case 'spectrum':
drawSpectrum(ctx, audioData);
break;
case 'kaleidoscope':
drawKaleidoscope(ctx, audioData);
break;
}
if (isPlaying) {
animationRef.current = requestAnimationFrame(animate);
}
};
useEffect(() => {
if (isPlaying) {
animate();
} else if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isPlaying, mode, intensity]);
const togglePlay = async () => {
if (!isPlaying && !audioContextRef.current) {
await initAudio();
}
setIsPlaying(!isPlaying);
};
return (
<div className="w-full h-screen bg-black flex flex-col">
<div className="flex-1 relative">
<canvas
ref={canvasRef}
className="w-full h-full"
/>
</div>
<div className="bg-gray-900 p-4 space-y-4">
<div className="flex items-center justify-center gap-4">
<button
onClick={togglePlay}
className="bg-cyan-500 hover:bg-cyan-600 text-white p-4 rounded-full transition-colors"
>
{isPlaying ? <Pause size={24} /> : <Play size={24} />}
</button>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{modes.map((m) => {
const Icon = m.icon;
return (
<button
key={m.id}
onClick={() => setMode(m.id)}
className={`p-3 rounded-lg flex flex-col items-center gap-2 transition-colors ${
mode === m.id
? 'bg-cyan-500 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'
}`}
>
<Icon size={20} />
<span className="text-sm">{m.name}</span>
</button>
);
})}
</div>
<div className="space-y-2">
<label className="text-white text-sm flex items-center gap-2">
<Sparkles size={16} />
Intensity: {intensity}%
</label>
<input
type="range"
min="10"
max="100"
value={intensity}
onChange={(e) => setIntensity(Number(e.target.value))}
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
style={{
accentColor: '#06b6d4'
}}
/>
</div>
<div className="text-center text-gray-400 text-sm space-y-1">
<p>🎵 Play your music from YouTube/Spotify/etc alongside this visualizer</p>
<p>💡 Perfect for MagicLight A.I. - screen colors sync with the beat!</p>
<p>🎤 Grant microphone permission for audio-reactive mode</p>https://claude.ai/public/artifacts/2366f48b-b3de-4d5a-a05a-b432397df0a9
</div>
</div>https://claude.ai/public/artifacts/2366f48b-b3de-4d5a-a05a-b432397df0a9
</div>
);
};
export default MusicVisualizer;https://claude.ai/public/artifacts/2366f48b-b3de-4d5a-a05a-b432397df0a9
.png)
.png)
No comments:
Post a Comment