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}
playbackSpeed={playbackSpeed}
isLooping={isLooping}
seekPosition={seekPosition}
audioElementRef={audioRef}
onPlaybackEnd={() => {
setIsPlaying(false);
setCurrentTime(0);

View File

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