Circular Waveform
Real-time circular audio spectrum visualizer with dynamic rotation and color transitions
The CircularWaveform component provides a real-time circular audio spectrum visualization that displays frequency data as animated bars arranged in a circle. It features dynamic rotation that responds to audio activity, smooth color transitions, and multiple visualization states for different interaction modes.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="w-96 h-96">
<CircularWaveform
audioTrack={null}
isThinking={false}
/>
</div>Props Reference
| Prop | Type | Default |
|---|---|---|
size? | number | undefined |
isThinking? | boolean | false |
audioTrack? | MediaStreamTrack | null | null |
className? | string | "" |
color1? | string | #00D3F2 |
color2? | string | #E12AFB |
backgroundColor? | string | transparent |
sensitivity? | number | 1 |
rotationEnabled? | boolean | true |
numBars? | number | 64 |
barWidth? | number | 4 |
debug? | boolean | false |
Basic Usage
The CircularWaveform component can be used in different states depending on your application's needs. It automatically adapts its visualization based on the provided props.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="space-y-8">
<div>
<h4 className="text-sm font-medium mb-2">Idle State</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={false}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Thinking State</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
/>
</div>
</div>
</div>Visual Customization
Color Schemes
Customize the visual appearance with different color combinations that transition based on audio activity.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="space-y-8">
<div>
<h4 className="text-sm font-medium mb-2">Default Colors</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
color1="#00D3F2"
color2="#E12AFB"
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Blue to Green</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
color1="#3B82F6"
color2="#10B981"
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Orange to Red</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
color1="#F59E0B"
color2="#EF4444"
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Purple to Pink</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
color1="#8B5CF6"
color2="#EC4899"
/>
</div>
</div>
</div>Bar Configuration
Adjust the number and appearance of bars for different visual effects.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="space-y-8">
<div>
<h4 className="text-sm font-medium mb-2">Few Bars (32)</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
numBars={32}
barWidth={6}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Default Bars (64)</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
numBars={64}
barWidth={4}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Many Bars (128)</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
numBars={128}
barWidth={2}
/>
</div>
</div>
</div>Rotation Control
Control the rotation behavior of the visualization.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="space-y-8">
<div>
<h4 className="text-sm font-medium mb-2">Rotation Enabled (Default)</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={false}
rotationEnabled={true}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Rotation Disabled</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={false}
rotationEnabled={false}
/>
</div>
</div>
</div>Audio Integration
Basic Audio Connection
Connect the visualizer to an audio track for real-time visualization.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
function AudioExample() {
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);
}
};
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-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={audioTrack}
isThinking={false}
color1="#00D3F2"
color2="#E12AFB"
/>
</div>
<p className="text-sm text-gray-600">
Status: {isConnected ? 'Audio Connected' : 'No Audio'}
</p>
</div>
);
}
render(<AudioExample />);Sensitivity Adjustment
Fine-tune the audio sensitivity for different environments and use cases.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
function SensitivityExample() {
const [audioTrack, setAudioTrack] = React.useState(null);
const [sensitivity, setSensitivity] = React.useState(1);
React.useEffect(() => {
const connectAudio = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
setAudioTrack(stream.getAudioTracks()[0]);
} catch (error) {
console.error('Error accessing microphone:', error);
}
};
connectAudio();
}, []);
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">
Sensitivity: {sensitivity.toFixed(1)}
</label>
<input
type="range"
min="0.5"
max="2.0"
step="0.1"
value={sensitivity}
onChange={(e) => setSensitivity(parseFloat(e.target.value))}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>Less Sensitive</span>
<span>More Sensitive</span>
</div>
</div>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={audioTrack}
isThinking={false}
sensitivity={sensitivity}
color1="#00D3F2"
color2="#E12AFB"
/>
</div>
</div>
);
}
render(<SensitivityExample />);State Management
Thinking State
The thinking state shows a traveling pulse animation that moves around the circle, perfect for indicating processing or waiting states.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
function ThinkingExample() {
const [isThinking, setIsThinking] = React.useState(false);
return (
<div className="space-y-4">
<button
onClick={() => setIsThinking(!isThinking)}
className="px-4 py-2 bg-purple-500 text-white rounded"
>
{isThinking ? 'Stop Thinking' : 'Start Thinking'}
</button>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={isThinking}
color1="#8B5CF6"
color2="#EC4899"
/>
</div>
</div>
);
}
render(<ThinkingExample />);Dynamic State Transitions
Combine different states for complex interaction patterns.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
function StateTransitionExample() {
const [audioTrack, setAudioTrack] = React.useState(null);
const [isThinking, setIsThinking] = React.useState(false);
const [currentState, setCurrentState] = React.useState('idle');
React.useEffect(() => {
const connectAudio = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
setAudioTrack(stream.getAudioTracks()[0]);
} catch (error) {
console.error('Error accessing microphone:', error);
}
};
connectAudio();
}, []);
const cycleStates = () => {
const states = ['idle', 'thinking', 'audio'];
const currentIndex = states.indexOf(currentState);
const nextState = states[(currentIndex + 1) % states.length];
setCurrentState(nextState);
setIsThinking(nextState === 'thinking');
};
return (
<div className="space-y-4">
<div className="flex gap-2">
<button
onClick={cycleStates}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Cycle States
</button>
</div>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={currentState === 'audio' ? audioTrack : null}
isThinking={isThinking}
color1="#00D3F2"
color2="#E12AFB"
/>
</div>
<p className="text-sm text-gray-600">
Current State: <span className="font-medium">{currentState}</span>
</p>
</div>
);
}
render(<StateTransitionExample />);Advanced Configuration
Custom Sizing
Control the size of the visualizer with fixed dimensions or let it be responsive.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="space-y-8">
<div>
<h4 className="text-sm font-medium mb-2">Small (200px)</h4>
<div className="w-52 h-52 bg-gray-50 rounded-lg p-2">
<CircularWaveform
audioTrack={null}
isThinking={true}
size={200}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Medium (320px)</h4>
<div className="w-80 h-80 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
size={320}
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Large (480px)</h4>
<div className="w-96 h-96 bg-gray-50 rounded-lg p-6">
<CircularWaveform
audioTrack={null}
isThinking={true}
size={480}
/>
</div>
</div>
</div>Background Customization
Add custom backgrounds to integrate with your design system.
import { CircularWaveform } from "@pipecat-ai/voice-ui-kit";
<div className="space-y-8">
<div>
<h4 className="text-sm font-medium mb-2">Transparent Background</h4>
<div className="w-64 h-64 bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
backgroundColor="transparent"
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">White Background</h4>
<div className="w-64 h-64 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
backgroundColor="white"
/>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Dark Background</h4>
<div className="w-64 h-64 bg-gray-50 rounded-lg p-4">
<CircularWaveform
audioTrack={null}
isThinking={true}
backgroundColor="rgba(0, 0, 0, 0.8)"
color1="#60A5FA"
color2="#F472B6"
/>
</div>
</div>
</div>Technical Details
Audio Processing
The CircularWaveform uses the Web Audio API to analyze audio frequency data in real-time. It employs advanced audio processing techniques including:
- Voice-optimized filtering: Emphasizes speech frequencies (200-4000 Hz)
- Dynamic range compression: Ensures consistent visualization across different volume levels
- Frequency smoothing: Creates smooth transitions between frequency bands
- Temporal smoothing: Prevents jarring jumps in visualization
Performance Considerations
- The component uses
requestAnimationFramefor smooth 60fps animations - Canvas rendering is optimized with 2x scaling for crisp visuals on high-DPI displays
- Audio processing is optimized with downsampling and efficient algorithms
- Memory usage is minimized with reusable data buffers
Browser Compatibility
The component requires:
- Web Audio API support
- Canvas 2D context support
- MediaStream API support
Modern browsers (Chrome 14+, Firefox 25+, Safari 6+) are fully supported.
State Behavior
The visualizer has four distinct states:
- IDLE: Minimal animation with subtle bar movement
- THINKING: Traveling pulse animation that moves around the circle
- AUDIO: Real-time audio-reactive visualization with dynamic rotation
- COMPLETE: Static state (currently unused)
The rotation speed dynamically responds to audio activity, spinning faster during speech and slowing down during silence.