A cross-platform application for routing ElevenLabs Conversational AI audio through a virtual audio cable with real-time monitoring capabilities.
This project implements a "Double-Hop" audio architecture:
- Agent Loop: Microphone → ElevenLabs API → Virtual Cable
- Monitor Loop: Virtual Cable → Speakers (for verification)
The monitor loop allows you to hear what's being sent to the virtual cable. When deploying to production (e.g., Unreal Engine), you simply disable the monitor loop and connect your target application to the virtual cable.
brew install blackhole-2chOr download directly from BlackHole GitHub.
Download and install from VB-Audio.
# PulseAudio: Create a virtual sink
pactl load-module module-null-sink sink_name=VirtualCable sink_properties=device.description=VirtualCable
# To make it persistent, add to /etc/pulse/default.pa:
# load-module module-null-sink sink_name=VirtualCable sink_properties=device.description=VirtualCableRequires Python 3.10 or higher.
# Create virtual environment (recommended)
python -m venv venv
# Activate virtual environment
# macOS/Linux:
source venv/bin/activate
# Windows:
venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt- Sign up at ElevenLabs
- Create a Conversational AI Agent
- Get your API key and Agent ID
git clone <repository-url>
cd AICONS
# Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt# Copy the example env file
cp .env.example .env
# Edit .env with your credentials
nano .env # or use your preferred editorAdd your ElevenLabs credentials:
ELEVENLABS_API_KEY=your_actual_api_key_here
ELEVENLABS_AGENT_ID=your_actual_agent_id_here
python -m src.device_managerThis will output something like:
--- INPUT DEVICES (Microphones) ---
ID 0: MacBook Pro Microphone
Channels: 1, Sample Rate: 48000 Hz
--- OUTPUT DEVICES (Speakers/Virtual Cables) ---
ID 1: MacBook Pro Speakers
Channels: 2, Sample Rate: 48000 Hz
ID 2: BlackHole 2ch
Channels: 2, Sample Rate: 48000 Hz
DETECTED VIRTUAL CABLES:
- BlackHole 2ch (ID: 2)
CONFIGURATION INSTRUCTIONS
Update your config.yaml with these device IDs:
devices:
mic_id: 0 # MacBook Pro Microphone
cable_id: 2 # BlackHole 2ch
speaker_id: 1 # MacBook Pro Speakers
Edit config.yaml with your device IDs:
devices:
mic_id: 0 # Your microphone ID
cable_id: 2 # Virtual cable ID (BlackHole/VB-Cable)
speaker_id: 1 # Your speakers IDpython -m src.mainThe application features a modern dark purple theme with two main panels:
-
Left Panel (Controls):
- Profile image display
- Start/Stop Conversation button
- Pause button (visible when conversation is active)
- Show/Hide Debug Log toggle
-
Right Panel (Logs):
- Conversation Log (top): Timestamped messages with color-coded roles (user/agent)
- Debug Log (bottom, collapsible): System logs with level-based color coding
- Start Conversation: Connects to ElevenLabs and begins the audio session
- Pause Audio: Mutes the microphone (sends silence) without disconnecting
- Stop Conversation: Ends the session and resets everything
- Show/Hide Debug: Toggle the debug log panel visibility
- READY (Gray): Application initialized, ready to start
- CONNECTING (Orange): Establishing connection to ElevenLabs
- LIVE (Green): Active conversation in progress
- PAUSED (Blue): Audio muted but session still active
- STOPPING (Orange): Session ending
- STOPPED (Red): Session ended
- ERROR (Red): An error occurred
| Variable | Description |
|---|---|
ELEVENLABS_API_KEY |
Your ElevenLabs API key |
ELEVENLABS_AGENT_ID |
Your Conversational AI Agent ID |
devices:
mic_id: null # Microphone input device ID (required)
cable_id: null # Virtual cable output device ID (required)
speaker_id: null # Speaker output device ID (required only if monitor.enabled: true)Note: When monitor.enabled: false, the speaker_id is not required and can be omitted or left as null.
audio:
sample_rate: 16000 # Hz (ElevenLabs standard)
channels: 1 # Input channels (1 = mono for ElevenLabs)
output_channels: 2 # Output channels (2 for stereo virtual cables)
dtype: int16 # PCM 16-bit
buffer_size: 1024 # Frames per bufferNote: Most virtual cables (BlackHole 2ch, VB-Cable) require stereo output (output_channels: 2). The application automatically converts mono audio from ElevenLabs to stereo by duplicating the channel.
ui:
window_title: "ElevenLabs Agent Controller"
window_width: 400
window_height: 300monitor:
enabled: true # Enable/disable the monitor loopMonitor Modes:
enabled: true(default): Audio is routed from virtual cable → speakers. You will hear the agent's voice through your speakers. Useful for testing and verification.enabled: false: Monitor loop is disabled. Audio is only sent to the virtual cable, making it available for external applications like Unreal Engine. You will NOT hear audio through speakers.
When monitor is disabled:
- The
speaker_iddevice configuration becomes optional - The UI will show "Audio → Virtual Cable" indicator
- External apps can read from the virtual cable (e.g., BlackHole, VB-Cable)
debug:
verbose_audio: false # Print audio stream status
print_transcripts: true # Print conversation to consoleAICONS/
├── .env.example # Example environment file
├── .env # Your API credentials (create this)
├── config.yaml # Application configuration
├── requirements.txt # Python dependencies
├── README.md # This file
├── CLAUDE.md # Developer guidance for Claude AI
├── assets/
│ └── img.jpeg # Profile image for UI
└── src/
├── __init__.py
├── main.py # Application entry point & business logic
├── ui.py # Tkinter UI components (dark purple theme)
├── config.py # Configuration loader & validation
├── device_manager.py # Audio device discovery
├── audio_interface.py # ElevenLabs audio handler
└── monitor_loop.py # Virtual cable → speakers
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Microphone │ ──► │ ElevenLabs │ ──► │ Virtual │
│ (Input) │ │ API │ │ Cable │
└─────────────┘ └──────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ Speakers │
│ (Monitor) │
└─────────────┘
main.py: Application entry point and business logic orchestrationui.py: Complete Tkinter UI with dark purple theme, conversation/debug logs, and status indicatorsconfig.py: Loads and validates configuration with three-stage validationdevice_manager.py: Cross-platform audio device discovery and identificationaudio_interface.py: Custom ElevenLabsAudioInterfacefor device routing with mono-to-stereo conversionmonitor_loop.py: Pass-through from virtual cable to speakers
macOS:
brew install blackhole-2ch
# Restart audio services:
sudo launchctl kickstart -kp system/com.apple.audio.coreaudiodWindows: Download and install VB-Cable, then restart your computer.
Linux:
pactl load-module module-null-sink sink_name=VirtualCable sink_properties=device.description=VirtualCable- Check that
cable_idin config.yaml matches your virtual cable's ID - Verify
output_channelsmatches your virtual cable (usually 2 for stereo) - Verify the virtual cable is set to the same sample rate (16000 Hz or configure accordingly)
- Check your OS sound settings to ensure the correct output device is selected
- Grant microphone permission to Terminal/your IDE
- macOS: System Preferences → Security & Privacy → Microphone
- Windows: Settings → Privacy → Microphone
- Linux: Check PulseAudio/PipeWire permissions
- Verify
mic_idmatches your microphone inconfig.yaml
- Verify your API key is correct in
.env - Check your internet connection
- Verify the Agent ID exists in your ElevenLabs dashboard
If you hear distorted audio, ensure all components use the same sample rate:
- Check virtual cable sample rate in your OS audio settings
- Match
audio.sample_ratein config.yaml - ElevenLabs typically uses 16000 Hz
If audio is only playing in one ear or sounds distorted:
- Check
output_channelsin config.yaml matches your virtual cable - Most virtual cables are stereo (
output_channels: 2) - The app automatically converts mono ElevenLabs audio to stereo
When deploying with external applications like Unreal Engine:
Edit config.yaml and set:
monitor:
enabled: falseThis stops the audio from being routed to speakers and makes the virtual cable exclusively available to external apps.
In Unreal Engine (or other applications):
- Open audio input settings
- Select your virtual cable as the audio input device:
- macOS: "BlackHole 2ch"
- Windows: "CABLE Output (VB-Audio Virtual Cable)"
- Linux: "VirtualCable"
Microphone → ElevenLabs API → Virtual Cable → Unreal Engine (MetaHuman Lipsync)
When monitor is disabled, the UI will display:
- "Audio → Virtual Cable" indicator (purple) in the header
- This confirms audio is NOT being sent to speakers
- Audio is available on the virtual cable for external apps
When monitor is enabled:
- "Audio → Speakers" indicator (green) in the header
- You will hear audio through your speakers for verification
MIT License - See LICENSE file for details.