Voice UI Kit

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

PropTypeDefault
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 requestAnimationFrame for 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.