mirror of
https://github.com/JorySeverijnse/ui-fixer-supreme.git
synced 2026-01-29 18:08:38 +00:00
Changes
This commit is contained in:
parent
cdc0f6d45e
commit
d4f544168d
@ -3,6 +3,11 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
|||||||
import { useSettings } from '@/contexts/SettingsContext';
|
import { useSettings } from '@/contexts/SettingsContext';
|
||||||
import { useAudioAnalyzer } from '@/contexts/AudioAnalyzerContext';
|
import { useAudioAnalyzer } from '@/contexts/AudioAnalyzerContext';
|
||||||
|
|
||||||
|
// Get CSS variable value
|
||||||
|
function getCSSVar(name: string): string {
|
||||||
|
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||||
|
}
|
||||||
|
|
||||||
export function MiniOscilloscope() {
|
export function MiniOscilloscope() {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const animationRef = useRef<number>();
|
const animationRef = useRef<number>();
|
||||||
@ -23,15 +28,23 @@ export function MiniOscilloscope() {
|
|||||||
const width = canvas.width;
|
const width = canvas.width;
|
||||||
const height = canvas.height;
|
const height = canvas.height;
|
||||||
|
|
||||||
|
// Get theme colors
|
||||||
|
const primaryHsl = getCSSVar('--primary');
|
||||||
|
const primaryColor = primaryHsl ? `hsl(${primaryHsl})` : 'hsl(120, 100%, 50%)';
|
||||||
|
const primaryColorDim = primaryHsl ? `hsl(${primaryHsl} / 0.3)` : 'hsl(120, 100%, 50%, 0.3)';
|
||||||
|
const primaryColorFaint = primaryHsl ? `hsl(${primaryHsl} / 0.1)` : 'hsl(120, 100%, 50%, 0.1)';
|
||||||
|
const bgHsl = getCSSVar('--background');
|
||||||
|
const bgColor = bgHsl ? `hsl(${bgHsl} / 0.8)` : 'rgba(0, 0, 0, 0.6)';
|
||||||
|
|
||||||
// Clear with transparent background
|
// Clear with transparent background
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
// Draw background
|
// Draw background with theme color
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
|
ctx.fillStyle = bgColor;
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
// Draw grid lines
|
// Draw grid lines with theme color
|
||||||
ctx.strokeStyle = 'hsl(120, 100%, 50%, 0.1)';
|
ctx.strokeStyle = primaryColorFaint;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
|
|
||||||
// Vertical grid lines
|
// Vertical grid lines
|
||||||
@ -42,31 +55,29 @@ export function MiniOscilloscope() {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center line
|
// Center line with theme color
|
||||||
ctx.strokeStyle = 'hsl(120, 100%, 50%, 0.3)';
|
ctx.strokeStyle = primaryColorDim;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, height / 2);
|
ctx.moveTo(0, height / 2);
|
||||||
ctx.lineTo(width, height / 2);
|
ctx.lineTo(width, height / 2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw waveform from analyzer
|
// Draw waveform from analyzer or flat line
|
||||||
let hasAudio = false;
|
|
||||||
|
|
||||||
if (analyzerNode) {
|
if (analyzerNode) {
|
||||||
const bufferLength = analyzerNode.frequencyBinCount;
|
const bufferLength = analyzerNode.frequencyBinCount;
|
||||||
const dataArray = new Uint8Array(bufferLength);
|
const dataArray = new Uint8Array(bufferLength);
|
||||||
analyzerNode.getByteTimeDomainData(dataArray);
|
analyzerNode.getByteTimeDomainData(dataArray);
|
||||||
|
|
||||||
// Check if there's actual audio (not just silence)
|
// Check if there's actual audio (not just silence)
|
||||||
hasAudio = dataArray.some(v => Math.abs(v - 128) > 2);
|
const hasAudio = dataArray.some(v => Math.abs(v - 128) > 2);
|
||||||
|
|
||||||
|
ctx.strokeStyle = primaryColor;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.shadowColor = primaryColor;
|
||||||
|
ctx.shadowBlur = hasAudio ? 10 : 0;
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
if (hasAudio) {
|
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;
|
const sliceWidth = width / bufferLength;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
|
|
||||||
@ -82,28 +93,21 @@ export function MiniOscilloscope() {
|
|||||||
|
|
||||||
x += sliceWidth;
|
x += sliceWidth;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Flat line when no audio
|
||||||
|
ctx.moveTo(0, height / 2);
|
||||||
ctx.lineTo(width, height / 2);
|
ctx.lineTo(width, height / 2);
|
||||||
ctx.stroke();
|
|
||||||
ctx.shadowBlur = 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Draw idle animation when no audio
|
ctx.stroke();
|
||||||
if (!hasAudio) {
|
ctx.shadowBlur = 0;
|
||||||
const time = Date.now() / 1000;
|
} else {
|
||||||
ctx.strokeStyle = 'hsl(120, 100%, 50%, 0.5)';
|
// Flat line when no analyzer
|
||||||
ctx.lineWidth = 1;
|
ctx.strokeStyle = primaryColor;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, height / 2);
|
||||||
for (let i = 0; i < width; i++) {
|
ctx.lineTo(width, height / 2);
|
||||||
const y = height / 2 + Math.sin(i * 0.05 + time * 2) * 3;
|
|
||||||
if (i === 0) {
|
|
||||||
ctx.moveTo(i, y);
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(i, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +157,7 @@ export function MiniOscilloscope() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="fixed bottom-4 left-1/2 -translate-x-1/2 w-[350px] md:w-[500px] h-[70px] z-50 cursor-pointer group"
|
className="fixed bottom-6 left-1/2 -translate-x-1/2 w-[400px] md:w-[600px] h-[80px] z-50 cursor-pointer group"
|
||||||
title="Open Oscilloscope"
|
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)]">
|
<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)]">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useRef, useCallback } from 'react';
|
import { useEffect, useRef, useCallback } from 'react';
|
||||||
import type { AudioData } from '@/hooks/useAudioAnalyzer';
|
import type { AudioData } from '@/hooks/useAudioAnalyzer';
|
||||||
import type { OscilloscopeMode } from '@/hooks/useOscilloscopeRenderer';
|
import type { OscilloscopeMode } from '@/hooks/useOscilloscopeRenderer';
|
||||||
|
import { useAudioAnalyzer as useSharedAudioAnalyzer } from '@/contexts/AudioAnalyzerContext';
|
||||||
|
|
||||||
interface OscilloscopeDisplayProps {
|
interface OscilloscopeDisplayProps {
|
||||||
audioData: AudioData | null;
|
audioData: AudioData | null;
|
||||||
@ -33,6 +34,10 @@ export function OscilloscopeDisplay({
|
|||||||
const animationRef = useRef<number | null>(null);
|
const animationRef = useRef<number | null>(null);
|
||||||
const currentSampleRef = useRef(0);
|
const currentSampleRef = useRef(0);
|
||||||
const lastSeekPositionRef = useRef(0);
|
const lastSeekPositionRef = useRef(0);
|
||||||
|
const { analyzerNode: sharedAnalyzer } = useSharedAudioAnalyzer();
|
||||||
|
|
||||||
|
// Use shared analyzer for live audio (music player, sound effects)
|
||||||
|
const liveAnalyzer = sharedAnalyzer || micAnalyzer;
|
||||||
|
|
||||||
const drawGraticule = useCallback((ctx: CanvasRenderingContext2D) => {
|
const drawGraticule = useCallback((ctx: CanvasRenderingContext2D) => {
|
||||||
ctx.strokeStyle = '#00ff00';
|
ctx.strokeStyle = '#00ff00';
|
||||||
@ -52,7 +57,11 @@ export function OscilloscopeDisplay({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const drawFrame = useCallback(() => {
|
const drawFrame = useCallback(() => {
|
||||||
if ((!audioData && !micAnalyzer) || !canvasRef.current) return;
|
if (!canvasRef.current) return;
|
||||||
|
|
||||||
|
// Always allow drawing if we have live analyzer, even without audioData
|
||||||
|
const hasLiveSource = liveAnalyzer || micAnalyzer;
|
||||||
|
if (!audioData && !hasLiveSource) return;
|
||||||
|
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
@ -63,23 +72,33 @@ export function OscilloscopeDisplay({
|
|||||||
let endSample: number;
|
let endSample: number;
|
||||||
let samplesToAdvance: number = samplesPerFrame;
|
let samplesToAdvance: number = samplesPerFrame;
|
||||||
|
|
||||||
if (micAnalyzer) {
|
// Priority: micAnalyzer > liveAnalyzer (shared) > audioData (file)
|
||||||
// Real-time microphone data
|
const activeAnalyzer = micAnalyzer || liveAnalyzer;
|
||||||
const bufferLength = micAnalyzer.frequencyBinCount;
|
|
||||||
|
if (activeAnalyzer && !audioData) {
|
||||||
|
// Real-time audio data (mic or music player)
|
||||||
|
const bufferLength = activeAnalyzer.frequencyBinCount;
|
||||||
const dataArray = new Uint8Array(bufferLength);
|
const dataArray = new Uint8Array(bufferLength);
|
||||||
micAnalyzer.getByteTimeDomainData(dataArray);
|
activeAnalyzer.getByteTimeDomainData(dataArray);
|
||||||
|
|
||||||
|
// Clear to pure black
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||||
|
|
||||||
|
// Draw graticule first
|
||||||
|
drawGraticule(ctx);
|
||||||
|
|
||||||
// Convert to Float32Array-like for consistency
|
// Convert to Float32Array-like for consistency
|
||||||
const micData = new Float32Array(dataArray.length);
|
const liveData = new Float32Array(dataArray.length);
|
||||||
for (let i = 0; i < dataArray.length; i++) {
|
for (let i = 0; i < dataArray.length; i++) {
|
||||||
micData[i] = (dataArray[i] - 128) / 128; // Normalize to -1 to 1
|
liveData[i] = (dataArray[i] - 128) / 128; // Normalize to -1 to 1
|
||||||
}
|
}
|
||||||
|
|
||||||
samplesPerFrame = micData.length;
|
samplesPerFrame = liveData.length;
|
||||||
startSample = 0;
|
startSample = 0;
|
||||||
endSample = micData.length;
|
endSample = liveData.length;
|
||||||
|
|
||||||
// Draw mic data directly
|
// Draw live data directly
|
||||||
ctx.strokeStyle = '#00ff00';
|
ctx.strokeStyle = '#00ff00';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@ -88,7 +107,7 @@ export function OscilloscopeDisplay({
|
|||||||
let x = 0;
|
let x = 0;
|
||||||
|
|
||||||
for (let i = 0; i < samplesPerFrame; i++) {
|
for (let i = 0; i < samplesPerFrame; i++) {
|
||||||
const v = micData[i];
|
const v = liveData[i];
|
||||||
const y = (v * HEIGHT) / 2 + HEIGHT / 2;
|
const y = (v * HEIGHT) / 2 + HEIGHT / 2;
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
@ -102,15 +121,13 @@ export function OscilloscopeDisplay({
|
|||||||
|
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw graticule
|
|
||||||
drawGraticule(ctx);
|
|
||||||
|
|
||||||
// Request next frame for real-time
|
// Request next frame for real-time
|
||||||
if (isPlaying) {
|
animationRef.current = requestAnimationFrame(drawFrame);
|
||||||
animationRef.current = requestAnimationFrame(drawFrame);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File playback mode - need audioData
|
||||||
|
if (!audioData) return;
|
||||||
|
|
||||||
// File playback mode
|
// File playback mode
|
||||||
const baseSamplesPerFrame = Math.floor(audioData.sampleRate / FPS);
|
const baseSamplesPerFrame = Math.floor(audioData.sampleRate / FPS);
|
||||||
@ -274,7 +291,7 @@ export function OscilloscopeDisplay({
|
|||||||
}
|
}
|
||||||
|
|
||||||
animationRef.current = requestAnimationFrame(drawFrame);
|
animationRef.current = requestAnimationFrame(drawFrame);
|
||||||
}, [audioData, micAnalyzer, mode, drawGraticule, onPlaybackEnd, isPlaying, playbackSpeed, isLooping, seekPosition]);
|
}, [audioData, micAnalyzer, liveAnalyzer, mode, drawGraticule, onPlaybackEnd, isPlaying, playbackSpeed, isLooping, seekPosition]);
|
||||||
|
|
||||||
// Initialize canvas
|
// Initialize canvas
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -288,11 +305,17 @@ export function OscilloscopeDisplay({
|
|||||||
}
|
}
|
||||||
}, [drawGraticule]);
|
}, [drawGraticule]);
|
||||||
|
|
||||||
// Handle playback
|
// Handle playback - start animation for file playback or live audio
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const hasLiveSource = liveAnalyzer || micAnalyzer;
|
||||||
|
|
||||||
if (isPlaying && audioData) {
|
if (isPlaying && audioData) {
|
||||||
|
// File playback
|
||||||
currentSampleRef.current = 0;
|
currentSampleRef.current = 0;
|
||||||
animationRef.current = requestAnimationFrame(drawFrame);
|
animationRef.current = requestAnimationFrame(drawFrame);
|
||||||
|
} else if (hasLiveSource && !audioData) {
|
||||||
|
// Live audio visualization (music player, sound effects)
|
||||||
|
animationRef.current = requestAnimationFrame(drawFrame);
|
||||||
} else {
|
} else {
|
||||||
if (animationRef.current) {
|
if (animationRef.current) {
|
||||||
cancelAnimationFrame(animationRef.current);
|
cancelAnimationFrame(animationRef.current);
|
||||||
@ -304,7 +327,7 @@ export function OscilloscopeDisplay({
|
|||||||
cancelAnimationFrame(animationRef.current);
|
cancelAnimationFrame(animationRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isPlaying, audioData, drawFrame]);
|
}, [isPlaying, audioData, liveAnalyzer, micAnalyzer, drawFrame]);
|
||||||
|
|
||||||
const getModeLabel = () => {
|
const getModeLabel = () => {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
@ -338,8 +361,8 @@ export function OscilloscopeDisplay({
|
|||||||
{getModeLabel()}
|
{getModeLabel()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Idle state */}
|
{/* Idle state - only show if no live audio and no file */}
|
||||||
{!audioData && !isPlaying && (
|
{!audioData && !liveAnalyzer && !micAnalyzer && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<p className="font-crt text-2xl text-primary/40 text-glow animate-pulse">
|
<p className="font-crt text-2xl text-primary/40 text-glow animate-pulse">
|
||||||
NO SIGNAL
|
NO SIGNAL
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user