Improve oscilloscope init and sync

- Ensure oscilloscope starts immediately and animates at full FPS
- Sync visualization with audio element without relying on slow timeupdate
- Update OscilloscopeDisplay to read from audio element directly and handle seek properly
- Adjust playback integration in Oscilloscope.tsx to pass proper refs and props

X-Lovable-Edit-ID: edt-073ab13c-7eb6-4c9d-b2fd-ac84088b119a
This commit is contained in:
gpt-engineer-app[bot] 2025-12-21 15:00:46 +00:00
commit 6693f94b65
2 changed files with 18 additions and 28 deletions

View File

@ -338,7 +338,7 @@ export function Oscilloscope() {
isPlaying={isPlaying} isPlaying={isPlaying}
playbackSpeed={playbackSpeed} playbackSpeed={playbackSpeed}
isLooping={isLooping} isLooping={isLooping}
seekPosition={seekPosition} audioElementRef={audioRef}
onPlaybackEnd={() => { onPlaybackEnd={() => {
setIsPlaying(false); setIsPlaying(false);
setCurrentTime(0); setCurrentTime(0);

View File

@ -11,7 +11,7 @@ interface OscilloscopeDisplayProps {
isPlaying: boolean; isPlaying: boolean;
playbackSpeed: number; playbackSpeed: number;
isLooping: boolean; isLooping: boolean;
seekPosition: number; audioElementRef?: React.RefObject<HTMLAudioElement | null>;
onPlaybackEnd?: () => void; onPlaybackEnd?: () => void;
onSeek?: (position: number) => void; onSeek?: (position: number) => void;
liveSettings?: LiveDisplaySettings; liveSettings?: LiveDisplaySettings;
@ -39,15 +39,13 @@ export function OscilloscopeDisplay({
isPlaying, isPlaying,
playbackSpeed, playbackSpeed,
isLooping, isLooping,
seekPosition, audioElementRef,
onPlaybackEnd, onPlaybackEnd,
onSeek, onSeek,
liveSettings liveSettings
}: OscilloscopeDisplayProps) { }: OscilloscopeDisplayProps) {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const animationRef = useRef<number | null>(null); const animationRef = useRef<number | null>(null);
const currentSampleRef = useRef(0);
const lastSeekPositionRef = useRef(0);
const { analyzerNode: sharedAnalyzer } = useSharedAudioAnalyzer(); const { analyzerNode: sharedAnalyzer } = useSharedAudioAnalyzer();
// Use shared analyzer for live audio (music player, sound effects) // Use shared analyzer for live audio (music player, sound effects)
@ -96,10 +94,9 @@ export function OscilloscopeDisplay({
const primaryColor = getThemeColor('--primary', '#00ff00'); const primaryColor = getThemeColor('--primary', '#00ff00');
const backgroundColor = getThemeColor('--background', '#000000'); const backgroundColor = getThemeColor('--background', '#000000');
let samplesPerFrame: number; let samplesPerFrame: number = 0;
let startSample: number; let endSample: number = 0;
let endSample: number; let samplesToAdvance: number = 0;
let samplesToAdvance: number = samplesPerFrame;
// Priority: micAnalyzer > liveAnalyzer (shared) > audioData (file) // Priority: micAnalyzer > liveAnalyzer (shared) > audioData (file)
const activeAnalyzer = micAnalyzer || liveAnalyzer; const activeAnalyzer = micAnalyzer || liveAnalyzer;
@ -180,26 +177,22 @@ export function OscilloscopeDisplay({
animationRef.current = requestAnimationFrame(drawFrame); animationRef.current = requestAnimationFrame(drawFrame);
return; return;
} }
// File playback mode - need audioData // File playback mode - need audioData
if (!audioData) return; if (!audioData) return;
// File playback mode // File playback mode - sync with audio element if available
const baseSamplesPerFrame = Math.floor(audioData.sampleRate / FPS); const baseSamplesPerFrame = Math.floor(audioData.sampleRate / FPS);
samplesPerFrame = Math.floor(baseSamplesPerFrame * playbackSpeed); samplesPerFrame = Math.floor(baseSamplesPerFrame * playbackSpeed);
samplesToAdvance = samplesPerFrame; samplesToAdvance = samplesPerFrame;
// Handle seeking // Get current position from audio element (real-time sync at 60fps)
if (seekPosition > 0 && seekPosition !== lastSeekPositionRef.current) { let startSample: number;
startSample = Math.floor(seekPosition * audioData.leftChannel.length); if (audioElementRef?.current && !audioElementRef.current.paused) {
currentSampleRef.current = startSample; const currentTime = audioElementRef.current.currentTime;
lastSeekPositionRef.current = seekPosition; startSample = Math.floor((currentTime / audioData.duration) * audioData.leftChannel.length);
// Reset after one frame
setTimeout(() => {
lastSeekPositionRef.current = 0;
}, 1000 / FPS);
} else { } else {
startSample = currentSampleRef.current; // Fallback: just show first frame when paused
startSample = 0;
} }
endSample = Math.min(startSample + samplesPerFrame, audioData.leftChannel.length); endSample = Math.min(startSample + samplesPerFrame, audioData.leftChannel.length);
@ -342,20 +335,18 @@ export function OscilloscopeDisplay({
ctx.stroke(); ctx.stroke();
} }
currentSampleRef.current = endSample;
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
if (endSample >= audioData.leftChannel.length) { // Check if audio ended (when syncing to audio element)
if (isLooping) { if (audioElementRef?.current) {
currentSampleRef.current = 0; // Loop back to start if (audioElementRef.current.ended && !isLooping) {
} else {
onPlaybackEnd?.(); onPlaybackEnd?.();
return; return;
} }
} }
animationRef.current = requestAnimationFrame(drawFrame); animationRef.current = requestAnimationFrame(drawFrame);
}, [audioData, micAnalyzer, liveAnalyzer, mode, drawGraticule, onPlaybackEnd, isPlaying, playbackSpeed, isLooping, seekPosition, lineThickness, glowIntensity, liveDisplayMode]); }, [audioData, micAnalyzer, liveAnalyzer, mode, drawGraticule, onPlaybackEnd, isPlaying, playbackSpeed, isLooping, lineThickness, glowIntensity, liveDisplayMode, audioElementRef]);
// Initialize canvas // Initialize canvas
useEffect(() => { useEffect(() => {
@ -375,7 +366,6 @@ export function OscilloscopeDisplay({
if (isPlaying && audioData) { if (isPlaying && audioData) {
// File playback // File playback
currentSampleRef.current = 0;
animationRef.current = requestAnimationFrame(drawFrame); animationRef.current = requestAnimationFrame(drawFrame);
} else if (hasLiveSource && !audioData) { } else if (hasLiveSource && !audioData) {
// Live audio visualization (music player, sound effects) // Live audio visualization (music player, sound effects)