Plasma Visualizer (WebGL)
WebGL-powered plasma effect visualizer with real-time audio reactivity and customizable rings
⚠️ WebGL & Three.js Required
The PlasmaVisualizer component requires WebGL support and Three.js to be installed in your project. Make sure to install Three.js as a dependency:
npm install three
# or
yarn add three
# or
pnpm add threeThe PlasmaVisualizer component provides a stunning WebGL-powered plasma effect visualization that responds to audio input in real-time. It features customizable plasma effects, animated rings, and extensive configuration options for creating immersive audio-reactive experiences.
import { PlasmaVisualizer } from "@pipecat-ai/voice-ui-kit/webgl";
<div className="w-full h-96 relative">
<PlasmaVisualizer />
</div>Props Reference
The PlasmaVisualizer component doesn't accept props directly. Instead, it automatically connects to the Pipecat client and responds to transport state changes. The component uses three predefined configurations:
- Default: Disconnected state with subtle plasma effects
- Connected: Ready state with enhanced effects
- Thinking: Active state with dynamic ring animations
Basic Usage
The PlasmaVisualizer automatically integrates with the Pipecat client and responds to connection state changes.
import { PlasmaVisualizer } from "@pipecat-ai/voice-ui-kit/webgl";
<div className="w-full h-96 relative bg-black rounded-lg overflow-hidden">
<PlasmaVisualizer />
</div>Advanced Configuration
For more control over the plasma effects, you can use the underlying Plasma component directly with custom configurations.
Custom Plasma Configuration
import { Plasma } from "@pipecat-ai/voice-ui-kit/webgl";
function CustomPlasmaExample() {
const plasmaRef = React.useRef(null);
const [config, setConfig] = React.useState({
intensity: 2.0,
radius: 1.5,
effectScale: 0.6,
ringCount: 4,
ringVisibility: 0.8,
ringDistance: 0.1,
ringBounce: 0.3,
ringThickness: 15,
ringVariance: 0.7,
ringAmplitude: 0.05,
ringSpeed: 2.0,
ringSegments: 6,
colorCycleSpeed: 1.0,
plasmaSpeed: 1.5,
useCustomColors: true,
color1: "#ff6b6b",
color2: "#4ecdc4",
color3: "#45b7d1",
backgroundColor: "transparent",
glowFalloff: 1.2,
glowThreshold: 0.1,
});
const updateConfig = (newConfig) => {
setConfig(newConfig);
plasmaRef.current?.updateConfig(newConfig);
};
return (
<div className="space-y-4">
<div className="w-full h-96 relative bg-black rounded-lg overflow-hidden">
<Plasma
ref={plasmaRef}
initialConfig={config}
className="absolute inset-0"
/>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<label className="block font-medium mb-1">Intensity</label>
<input
type="range"
min="0.5"
max="3.0"
step="0.1"
value={config.intensity}
onChange={(e) => updateConfig({...config, intensity: parseFloat(e.target.value)})}
className="w-full"
/>
<span className="text-gray-500">{config.intensity.toFixed(1)}</span>
</div>
<div>
<label className="block font-medium mb-1">Radius</label>
<input
type="range"
min="0.5"
max="3.0"
step="0.1"
value={config.radius}
onChange={(e) => updateConfig({...config, radius: parseFloat(e.target.value)})}
className="w-full"
/>
<span className="text-gray-500">{config.radius.toFixed(1)}</span>
</div>
<div>
<label className="block font-medium mb-1">Ring Count</label>
<input
type="range"
min="0"
max="8"
step="1"
value={config.ringCount}
onChange={(e) => updateConfig({...config, ringCount: parseInt(e.target.value)})}
className="w-full"
/>
<span className="text-gray-500">{config.ringCount}</span>
</div>
<div>
<label className="block font-medium mb-1">Ring Visibility</label>
<input
type="range"
min="0"
max="1"
step="0.1"
value={config.ringVisibility}
onChange={(e) => updateConfig({...config, ringVisibility: parseFloat(e.target.value)})}
className="w-full"
/>
<span className="text-gray-500">{config.ringVisibility.toFixed(1)}</span>
</div>
</div>
</div>
);
}
render(<CustomPlasmaExample />);Color Customization
import { Plasma } from "@pipecat-ai/voice-ui-kit/webgl";
function ColorExample() {
const plasmaRef = React.useRef(null);
const [colorScheme, setColorScheme] = React.useState('ocean');
const schemes = {
ocean: {
color1: "#22d3ee",
color2: "#34d399",
color3: "#818cf8",
},
sunset: {
color1: "#ff6b6b",
color2: "#ffa726",
color3: "#ff5722",
},
forest: {
color1: "#4caf50",
color2: "#8bc34a",
color3: "#cddc39",
},
cosmic: {
color1: "#9c27b0",
color2: "#e91e63",
color3: "#ff4081",
},
};
const updateColorScheme = (scheme) => {
setColorScheme(scheme);
const newConfig = {
intensity: 1.8,
radius: 1.7,
ringCount: 3,
ringVisibility: 0.6,
ringBounce: 0.2,
ringThickness: 12,
ringVariance: 0.5,
ringAmplitude: 0.03,
ringSpeed: 1.5,
ringSegments: 5,
colorCycleSpeed: 0.8,
plasmaSpeed: 1.2,
useCustomColors: true,
...schemes[scheme],
backgroundColor: "transparent",
};
plasmaRef.current?.updateConfig(newConfig);
};
const config = {
intensity: 1.8,
radius: 1.7,
ringCount: 3,
ringVisibility: 0.6,
ringBounce: 0.2,
ringThickness: 12,
ringVariance: 0.5,
ringAmplitude: 0.03,
ringSpeed: 1.5,
ringSegments: 5,
colorCycleSpeed: 0.8,
plasmaSpeed: 1.2,
useCustomColors: true,
...schemes[colorScheme],
backgroundColor: "transparent",
};
return (
<div className="space-y-4">
<div className="flex gap-2">
{Object.keys(schemes).map((scheme) => (
<button
key={scheme}
onClick={() => updateColorScheme(scheme)}
className={`px-3 py-1 rounded text-sm capitalize ${
colorScheme === scheme
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-700'
}`}
>
{scheme}
</button>
))}
</div>
<div className="w-full h-96 relative bg-black rounded-lg overflow-hidden">
<Plasma
ref={plasmaRef}
initialConfig={config}
className="absolute inset-0"
/>
</div>
</div>
);
}
render(<ColorExample />);Audio-Reactive Configuration
import { Plasma } from "@pipecat-ai/voice-ui-kit/webgl";
function AudioReactiveExample() {
const plasmaRef = React.useRef(null);
const [audioTrack, setAudioTrack] = React.useState(null);
const [isConnected, setIsConnected] = React.useState(false);
const connectAudio = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
setAudioTrack(stream.getAudioTracks()[0]);
setIsConnected(true);
} catch (error) {
console.error('Error accessing microphone:', error);
}
};
const disconnectAudio = () => {
if (audioTrack) {
audioTrack.stop();
setAudioTrack(null);
setIsConnected(false);
}
};
const config = {
intensity: 2.2,
radius: 1.8,
effectScale: 0.55,
ringCount: 5,
ringVisibility: 0.7,
ringDistance: 0.05,
ringBounce: 0.4,
ringThickness: 18,
ringVariance: 0.8,
ringAmplitude: 0.08,
ringSpeed: 2.5,
ringSegments: 6,
colorCycleSpeed: 1.2,
plasmaSpeed: 2.0,
useCustomColors: true,
color1: "#ff0080",
color2: "#00ff80",
color3: "#8000ff",
backgroundColor: "transparent",
glowFalloff: 1.5,
glowThreshold: 0.05,
// Audio reactivity settings
audioEnabled: true,
audioSensitivity: 1.5,
audioSmoothing: 0.8,
frequencyBands: 32,
bassResponse: 1.5,
midResponse: 1.2,
trebleResponse: 1.0,
plasmaVolumeReactivity: 3.0,
volumeThreshold: 0.1,
};
return (
<div className="space-y-4">
<div className="flex gap-2">
<button
onClick={connectAudio}
disabled={isConnected}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
Connect Audio
</button>
<button
onClick={disconnectAudio}
disabled={!isConnected}
className="px-4 py-2 bg-red-500 text-white rounded disabled:opacity-50"
>
Disconnect Audio
</button>
</div>
<div className="w-full h-96 relative bg-black rounded-lg overflow-hidden">
<Plasma
ref={plasmaRef}
initialConfig={config}
audioTrack={audioTrack}
className="absolute inset-0"
/>
</div>
<p className="text-sm text-gray-600">
Status: {isConnected ? 'Audio Connected - Try speaking!' : 'No Audio'}
</p>
</div>
);
}
render(<AudioReactiveExample />);Configuration Options
The Plasma component accepts extensive configuration options for fine-tuning the visual effects:
Core Plasma Properties
intensity: Overall brightness and strength of the plasma effect (0.5-3.0)radius: Size of the plasma effect (0.5-3.0)effectScale: Scaling factor for the entire effect (0.1-2.0)effectCenter: Center point of the effect ({ x: number, y: number })blendMode: Color blending mode (0-5)plasmaSpeed: Animation speed of the plasma (0.1-5.0)rayLength: Length of plasma rays (0.1-2.0)
Ring Properties
ringCount: Number of animated rings (0-8)ringVisibility: Opacity of rings (0-1)ringDistance: Distance between rings (0-0.5)ringSpread: Spacing between rings (0-0.2)ringBounce: Bouncing animation intensity (0-1)ringThickness: Thickness of ring lines (1-50)ringVariance: Random variation in ring properties (0-1)ringSharpness: Edge sharpness of rings (0-1)ringAmplitude: Wave amplitude of rings (0-0.2)ringSpeed: Animation speed of rings (0.1-5.0)ringSegments: Number of segments per ring (1-20)ringColorInheritance: How rings inherit plasma colors (0-1)
Color Properties
useCustomColors: Whether to use custom colors or rainbow modecolor1: Primary color (hex string)color2: Secondary color (hex string)color3: Tertiary color (hex string)backgroundColor: Background color (hex string)colorCycleSpeed: Speed of color transitions (0.1-3.0)
Glow Properties
glowFalloff: How quickly glow fades (0.1-3.0)glowThreshold: Minimum glow intensity (0-1)
Audio Properties
audioEnabled: Enable audio reactivityaudioSensitivity: Audio input sensitivity (0.1-3.0)audioSmoothing: Smoothing factor for audio data (0.1-1.0)frequencyBands: Number of frequency bands to analyze (8-64)bassResponse: Response to bass frequencies (0.1-3.0)midResponse: Response to mid frequencies (0.1-3.0)trebleResponse: Response to treble frequencies (0.1-3.0)plasmaVolumeReactivity: How much volume affects plasma (0.1-5.0)volumeThreshold: Minimum volume to trigger effects (0-1)
Technical Details
WebGL Requirements
The PlasmaVisualizer requires:
- WebGL 1.0 or 2.0 support in the browser
- Three.js library installed as a dependency
- Modern browser with WebGL context support
Performance Considerations
- The component uses WebGL shaders for optimal performance
- Automatic pixel ratio detection for crisp rendering on high-DPI displays
- Visibility-based rendering - pauses when tab is not visible
- Context loss handling - gracefully recovers from WebGL context loss
- ResizeObserver for efficient canvas resizing
Browser Compatibility
- Chrome 9+: Full support
- Firefox 4+: Full support
- Safari 5.1+: Full support
- Edge 12+: Full support
Audio Processing
The component uses the Web Audio API for real-time audio analysis:
- FFT analysis for frequency domain processing
- Multi-band filtering for bass, mid, and treble response
- Smoothing algorithms to prevent jarring visual changes
- Volume thresholding to avoid noise-triggered effects
Shader Details
The plasma effect is generated using custom GLSL fragment shaders that create:
- Simplex noise for organic plasma movement
- Radial gradients for the core plasma shape
- Ring wave functions for animated ring effects
- Color blending with multiple blend modes
- Audio-reactive parameters that modify shader uniforms in real-time