This commit is contained in:
gpt-engineer-app[bot]
2025-12-21 13:55:16 +00:00
parent 3f05ec4015
commit a9235fdb3f
5 changed files with 229 additions and 125 deletions
+36 -93
View File
@@ -1,68 +1,15 @@
import { useEffect, useRef, useCallback } from 'react';
import { useEffect, useRef } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useSettings } from '@/contexts/SettingsContext';
import { useMusic } from '@/contexts/MusicContext';
import { useAudioAnalyzer } from '@/contexts/AudioAnalyzerContext';
export function MiniOscilloscope() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const animationRef = useRef<number>();
const analyzerRef = useRef<AnalyserNode | null>(null);
const audioContextRef = useRef<AudioContext | null>(null);
const sourceNodeRef = useRef<MediaElementAudioSourceNode | null>(null);
const connectedElementRef = useRef<HTMLAudioElement | null>(null);
const navigate = useNavigate();
const location = useLocation();
const { playSound } = useSettings();
const { audioElement, isPlaying } = useMusic();
// Connect to music player's audio element
useEffect(() => {
if (!audioElement) return;
// Skip if already connected to this element
if (connectedElementRef.current === audioElement) return;
const connectToAudio = async () => {
try {
// Create or resume audio context
if (!audioContextRef.current) {
audioContextRef.current = new AudioContext();
}
if (audioContextRef.current.state === 'suspended') {
await audioContextRef.current.resume();
}
// Create analyzer if needed
if (!analyzerRef.current) {
analyzerRef.current = audioContextRef.current.createAnalyser();
analyzerRef.current.fftSize = 256;
analyzerRef.current.smoothingTimeConstant = 0.8;
}
// Disconnect old source if exists
if (sourceNodeRef.current) {
try {
sourceNodeRef.current.disconnect();
} catch (e) {
// Ignore disconnect errors
}
}
// Create new source from the audio element
sourceNodeRef.current = audioContextRef.current.createMediaElementSource(audioElement);
sourceNodeRef.current.connect(analyzerRef.current);
sourceNodeRef.current.connect(audioContextRef.current.destination);
connectedElementRef.current = audioElement;
console.log('MiniOscilloscope connected to audio element');
} catch (e) {
console.log('Could not connect to audio element:', e);
}
};
connectToAudio();
}, [audioElement]);
const { analyzerNode } = useAudioAnalyzer();
// Draw waveform
useEffect(() => {
@@ -102,42 +49,48 @@ export function MiniOscilloscope() {
ctx.lineTo(width, height / 2);
ctx.stroke();
// Draw waveform
if (analyzerRef.current && isPlaying) {
const bufferLength = analyzerRef.current.frequencyBinCount;
// Draw waveform from analyzer
let hasAudio = false;
if (analyzerNode) {
const bufferLength = analyzerNode.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyzerRef.current.getByteTimeDomainData(dataArray);
analyzerNode.getByteTimeDomainData(dataArray);
// Check if there's actual audio (not just silence)
const hasAudio = dataArray.some(v => Math.abs(v - 128) > 2);
hasAudio = dataArray.some(v => Math.abs(v - 128) > 2);
ctx.strokeStyle = 'hsl(120, 100%, 50%)';
ctx.lineWidth = 2;
ctx.shadowColor = 'hsl(120, 100%, 50%)';
ctx.shadowBlur = hasAudio ? 10 : 5;
ctx.beginPath();
if (hasAudio) {
ctx.strokeStyle = 'hsl(120, 100%, 50%)';
ctx.lineWidth = 2;
ctx.shadowColor = 'hsl(120, 100%, 50%)';
ctx.shadowBlur = 10;
ctx.beginPath();
const sliceWidth = width / bufferLength;
let x = 0;
const sliceWidth = width / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = (v * height) / 2;
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = (v * height) / 2;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
x += sliceWidth;
}
x += sliceWidth;
ctx.lineTo(width, height / 2);
ctx.stroke();
ctx.shadowBlur = 0;
}
}
ctx.lineTo(width, height / 2);
ctx.stroke();
ctx.shadowBlur = 0;
} else {
// No audio playing - draw idle animation
// Draw idle animation when no audio
if (!hasAudio) {
const time = Date.now() / 1000;
ctx.strokeStyle = 'hsl(120, 100%, 50%, 0.5)';
ctx.lineWidth = 1;
@@ -164,7 +117,7 @@ export function MiniOscilloscope() {
cancelAnimationFrame(animationRef.current);
}
};
}, [isPlaying]);
}, [analyzerNode]);
// Handle resize
useEffect(() => {
@@ -187,16 +140,6 @@ export function MiniOscilloscope() {
};
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
// Don't close audio context as it would break the music player
};
}, []);
const handleClick = () => {
playSound('click');
navigate('/oscilloscope');
@@ -210,7 +153,7 @@ export function MiniOscilloscope() {
return (
<div
onClick={handleClick}
className="fixed bottom-4 left-1/2 -translate-x-1/2 w-[300px] md:w-[400px] h-[50px] z-50 cursor-pointer group"
className="fixed bottom-4 left-1/2 -translate-x-1/2 w-[350px] md:w-[500px] h-[70px] z-50 cursor-pointer group"
title="Open Oscilloscope"
>
<div className="relative w-full h-full rounded-lg border border-primary/50 overflow-hidden bg-background/80 backdrop-blur-sm transition-all duration-300 group-hover:border-primary group-hover:shadow-[0_0_20px_hsl(var(--primary)/0.4)]">