-
+
+
+ {/* Display Mode */}
onModeChange(value as OscilloscopeMode)}
+ value={liveSettings.displayMode}
+ onValueChange={(value) => onLiveSettingsChange({ ...liveSettings, displayMode: value as OscilloscopeMode })}
className="space-y-2"
>
-
-
-
-
+
+
Separate (L/R stacked)
-
-
+
+
All (L/R + XY below)
+
+ {/* Line Thickness */}
+
+
Line Thickness
+
+ {[1, 2, 3, 4].map((thickness) => (
+
+ ))}
+
+
+
+ {/* Show Grid */}
+
+
Show Grid
+
+
+
+ {/* Glow Intensity */}
+
+
Glow
+
+ {[0, 1, 2, 3].map((glow) => (
+
+ ))}
+
+
{/* Playback Controls */}
@@ -141,8 +211,23 @@ export function ControlPanel({
{/* Export Options */}
-
+
EXPORT OPTIONS
+
Settings for video export only
+
+ {/* Export Display Mode */}
+
+ Mode
+
+
{/* Resolution */}
diff --git a/src/components/Oscilloscope.tsx b/src/components/Oscilloscope.tsx
index 212da4f..0bf0904 100644
--- a/src/components/Oscilloscope.tsx
+++ b/src/components/Oscilloscope.tsx
@@ -1,6 +1,6 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { AudioUploader } from './AudioUploader';
-import { ControlPanel } from './ControlPanel';
+import { ControlPanel, LiveDisplaySettings } from './ControlPanel';
import { OscilloscopeDisplay } from './OscilloscopeDisplay';
import { useAudioAnalyzer } from '@/hooks/useAudioAnalyzer';
import { useOscilloscopeRenderer } from '@/hooks/useOscilloscopeRenderer';
@@ -11,6 +11,12 @@ import { Button } from '@/components/ui/button';
export function Oscilloscope() {
const [mode, setMode] = useState<'combined' | 'separate' | 'all'>('combined');
+ const [liveSettings, setLiveSettings] = useState({
+ lineThickness: 2,
+ showGrid: true,
+ glowIntensity: 1,
+ displayMode: 'combined',
+ });
const [isMicActive, setIsMicActive] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);
const [currentSample, setCurrentSample] = useState(0);
@@ -249,6 +255,8 @@ export function Oscilloscope() {
onExportFormatChange={handleExportFormatChange}
exportQuality={exportQuality}
onExportQualityChange={handleExportQualityChange}
+ liveSettings={liveSettings}
+ onLiveSettingsChange={setLiveSettings}
/>
@@ -257,13 +265,14 @@ export function Oscilloscope() {
setIsPlaying(false)}
onSeek={handleSeek}
+ liveSettings={liveSettings}
/>
diff --git a/src/components/OscilloscopeDisplay.tsx b/src/components/OscilloscopeDisplay.tsx
index 92323ea..97e3506 100755
--- a/src/components/OscilloscopeDisplay.tsx
+++ b/src/components/OscilloscopeDisplay.tsx
@@ -2,6 +2,7 @@ import { useEffect, useRef, useCallback } from 'react';
import type { AudioData } from '@/hooks/useAudioAnalyzer';
import type { OscilloscopeMode } from '@/hooks/useOscilloscopeRenderer';
import { useAudioAnalyzer as useSharedAudioAnalyzer } from '@/contexts/AudioAnalyzerContext';
+import type { LiveDisplaySettings } from './ControlPanel';
interface OscilloscopeDisplayProps {
audioData: AudioData | null;
@@ -13,12 +14,24 @@ interface OscilloscopeDisplayProps {
seekPosition: number;
onPlaybackEnd?: () => void;
onSeek?: (position: number) => void;
+ liveSettings?: LiveDisplaySettings;
}
const WIDTH = 800;
const HEIGHT = 600;
const FPS = 60;
+// Get computed CSS color from theme
+const getThemeColor = (cssVar: string, fallback: string): string => {
+ if (typeof window === 'undefined') return fallback;
+ const root = document.documentElement;
+ const value = getComputedStyle(root).getPropertyValue(cssVar).trim();
+ if (value) {
+ return `hsl(${value})`;
+ }
+ return fallback;
+};
+
export function OscilloscopeDisplay({
audioData,
micAnalyzer,
@@ -28,7 +41,8 @@ export function OscilloscopeDisplay({
isLooping,
seekPosition,
onPlaybackEnd,
- onSeek
+ onSeek,
+ liveSettings
}: OscilloscopeDisplayProps) {
const canvasRef = useRef
(null);
const animationRef = useRef(null);
@@ -39,8 +53,17 @@ export function OscilloscopeDisplay({
// Use shared analyzer for live audio (music player, sound effects)
const liveAnalyzer = sharedAnalyzer || micAnalyzer;
+ // Get settings with defaults
+ const lineThickness = liveSettings?.lineThickness ?? 2;
+ const showGrid = liveSettings?.showGrid ?? true;
+ const glowIntensity = liveSettings?.glowIntensity ?? 1;
+
const drawGraticule = useCallback((ctx: CanvasRenderingContext2D) => {
- ctx.strokeStyle = '#00ff00';
+ if (!showGrid) return;
+
+ const primaryColor = getThemeColor('--primary', '#00ff00');
+ ctx.strokeStyle = primaryColor;
+ ctx.globalAlpha = 0.3;
ctx.lineWidth = 1;
// Horizontal center line (X axis)
@@ -54,7 +77,9 @@ export function OscilloscopeDisplay({
ctx.moveTo(WIDTH / 2, 0);
ctx.lineTo(WIDTH / 2, HEIGHT);
ctx.stroke();
- }, []);
+
+ ctx.globalAlpha = 1;
+ }, [showGrid]);
const drawFrame = useCallback(() => {
if (!canvasRef.current) return;
@@ -67,6 +92,9 @@ export function OscilloscopeDisplay({
const ctx = canvas.getContext('2d');
if (!ctx) return;
+ const primaryColor = getThemeColor('--primary', '#00ff00');
+ const backgroundColor = getThemeColor('--background', '#000000');
+
let samplesPerFrame: number;
let startSample: number;
let endSample: number;
@@ -81,8 +109,8 @@ export function OscilloscopeDisplay({
const dataArray = new Uint8Array(bufferLength);
activeAnalyzer.getByteTimeDomainData(dataArray);
- // Clear to pure black
- ctx.fillStyle = '#000000';
+ // Clear to background color
+ ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// Draw graticule first
@@ -98,9 +126,17 @@ export function OscilloscopeDisplay({
startSample = 0;
endSample = liveData.length;
+ // Apply glow effect
+ if (glowIntensity > 0) {
+ ctx.shadowColor = primaryColor;
+ ctx.shadowBlur = glowIntensity * 8;
+ } else {
+ ctx.shadowBlur = 0;
+ }
+
// Draw live data directly
- ctx.strokeStyle = '#00ff00';
- ctx.lineWidth = 2;
+ ctx.strokeStyle = primaryColor;
+ ctx.lineWidth = lineThickness;
ctx.beginPath();
const sliceWidth = WIDTH / samplesPerFrame;
@@ -120,6 +156,7 @@ export function OscilloscopeDisplay({
}
ctx.stroke();
+ ctx.shadowBlur = 0;
// Request next frame for real-time
animationRef.current = requestAnimationFrame(drawFrame);
@@ -149,20 +186,28 @@ export function OscilloscopeDisplay({
endSample = Math.min(startSample + samplesPerFrame, audioData.leftChannel.length);
- // Clear to pure black
- ctx.fillStyle = '#000000';
+ // Clear to background color
+ ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// Draw graticule first
drawGraticule(ctx);
- ctx.lineWidth = 2;
+ // Apply glow effect
+ if (glowIntensity > 0) {
+ ctx.shadowColor = primaryColor;
+ ctx.shadowBlur = glowIntensity * 8;
+ } else {
+ ctx.shadowBlur = 0;
+ }
+
+ ctx.lineWidth = lineThickness;
ctx.lineCap = 'round';
- const leftColor = '#00ff00';
- const rightColor = '#00ccff';
- const xyColor = '#ff8800';
- const dividerColor = '#333333';
+ const leftColor = primaryColor;
+ const rightColor = getThemeColor('--accent', '#00ccff');
+ const xyColor = getThemeColor('--secondary', '#ff8800');
+ const dividerColor = 'rgba(255,255,255,0.1)';
if (mode === 'combined') {
// Combined: both channels merged
@@ -280,6 +325,7 @@ export function OscilloscopeDisplay({
}
currentSampleRef.current = endSample;
+ ctx.shadowBlur = 0;
if (endSample >= audioData.leftChannel.length) {
if (isLooping) {
@@ -291,7 +337,7 @@ export function OscilloscopeDisplay({
}
animationRef.current = requestAnimationFrame(drawFrame);
- }, [audioData, micAnalyzer, liveAnalyzer, mode, drawGraticule, onPlaybackEnd, isPlaying, playbackSpeed, isLooping, seekPosition]);
+ }, [audioData, micAnalyzer, liveAnalyzer, mode, drawGraticule, onPlaybackEnd, isPlaying, playbackSpeed, isLooping, seekPosition, lineThickness, glowIntensity]);
// Initialize canvas
useEffect(() => {