7.0 KiB
7.0 KiB
Python Soundboard — Claude Code Build Plan
Overview
A desktop soundboard application for Windows 11 that routes audio through VB-Cable so it appears as a microphone input to other applications (Discord, OBS, Teams, etc.). Users can add and remove audio files from the board at runtime via a simple GUI.
Tech Stack
| Layer | Choice | Reason |
|---|---|---|
| Language | Python 3.11+ | Easy iteration, good audio library support |
| GUI | tkinter |
Stdlib, no install needed, sufficient for this use case |
| Audio playback | sounddevice + soundfile |
Device selection by name, supports WAV/FLAC natively |
| Audio decoding | pydub + ffmpeg |
Converts MP3/OGG/etc. to raw PCM for sounddevice |
| Config persistence | json |
Store board layout and device preference between sessions |
Install Requirements (requirements.txt)
sounddevice
soundfile
pydub
numpy
ffmpeg must also be installed and on PATH (used by pydub for MP3 decoding). Recommend:
winget install Gyan.FFmpeg
Project Structure
soundboard/
├── main.py # Entry point, launches GUI
├── audio_engine.py # Handles device selection and playback
├── board.py # Board state: buttons, file mappings
├── config.py # Load/save config to JSON
├── config.json # Persisted state (auto-created on first run)
├── requirements.txt
└── sounds/ # Optional default folder for audio files
Feature Spec
1. Device Selection (Startup)
- On launch, scan available output devices via
sounddevice.query_devices() - Show a dropdown/listbox to select the output device
- Default: auto-select first device whose name contains
"CABLE Input"(VB-Cable) - Selection is saved to
config.jsonand restored on next launch
2. Soundboard Grid
- Display a grid of buttons (default: 4 columns, expandable rows)
- Each button shows:
- The audio file's base name (e.g.
airhorn.mp3) - A colored indicator when playing
- The audio file's base name (e.g.
- Clicking a button plays that sound through the selected output device
- Playing a sound does NOT stop other sounds (overlapping play supported)
- Optional: right-click a button → Stop this sound
3. Add Audio Files
- "Add Sound" button opens a file picker dialog
- Supported formats:
.wav,.mp3,.ogg,.flac,.aiff - File is registered on the board (path stored in config); it is NOT copied
- New button appears immediately in the grid
- Board layout is saved to
config.jsonautomatically
4. Remove Audio Files
- Right-click any button → context menu with "Remove" option
- Confirmation prompt before removal
- Button is removed from the grid; entry deleted from config
- Does not delete the file from disk
5. Rename Buttons
- Right-click → "Rename" option
- Simple text entry dialog
- Custom label saved to config (file path unchanged)
6. Volume Control
- A global volume slider (0–100%) in the toolbar
- Scales the numpy audio array before playback
- Per-button volume is a stretch goal (not in v1)
7. Stop All
- "Stop All" button immediately halts all active sounddevice streams
8. Config Persistence (config.json schema)
{
"output_device": "CABLE Input (VB-Audio Virtual Cable)",
"volume": 80,
"buttons": [
{
"id": "btn_001",
"label": "Air Horn",
"file_path": "C:/Users/user/sounds/airhorn.mp3"
},
{
"id": "btn_002",
"label": "Sad Trombone",
"file_path": "C:/Users/user/sounds/sad_trombone.wav"
}
]
}
Key Implementation Details for Claude Code
Audio Playback (audio_engine.py)
import sounddevice as sd
import soundfile as sf
import numpy as np
from pydub import AudioSegment
import io
def get_device_id(device_name: str) -> int:
"""Find output device index by name substring match."""
devices = sd.query_devices()
for i, dev in enumerate(devices):
if device_name.lower() in dev['name'].lower() and dev['max_output_channels'] > 0:
return i
raise ValueError(f"Device not found: {device_name}")
def load_audio(file_path: str) -> tuple[np.ndarray, int]:
"""Load any supported audio format, return (samples_array, samplerate)."""
ext = file_path.lower().split('.')[-1]
if ext in ('wav', 'flac', 'aiff'):
data, sr = sf.read(file_path, dtype='float32')
else:
# Use pydub for MP3/OGG/etc.
seg = AudioSegment.from_file(file_path)
seg = seg.set_channels(2).set_frame_rate(44100)
samples = np.array(seg.get_array_of_samples(), dtype=np.float32)
samples = samples.reshape((-1, 2)) / 32768.0
data, sr = samples, 44100
return data, sr
def play_sound(file_path: str, device_id: int, volume: float = 1.0):
"""Play audio non-blocking on the specified device."""
data, sr = load_audio(file_path)
data = data * volume # apply volume scaling
sd.play(data, samplerate=sr, device=device_id, blocking=False)
Threading Note
sd.play()is non-blocking by default but is not thread-safe when called rapidly- Use a
threading.Threadper button press to avoid GUI freezes on slow file loads - Keep a list of active streams if stop-all is needed
GUI Layout (main.py / board.py)
- Use
tkinter.ttkfor slightly nicer widgets - Grid of
tk.Buttonwidgets built dynamically from config tkinter.filedialog.askopenfilename()for file pickertkinter.simpledialog.askstring()for renametkinter.Menufor right-click context menus
VB-Cable Setup Instructions (include in README)
- Download and install VB-Cable from https://vb-audio.com/Cable
- Reboot after installation
- In the soundboard, select "CABLE Input (VB-Audio Virtual Cable)" as the output device
- In Discord / OBS / Teams, set the microphone input to "CABLE Output (VB-Audio Virtual Cable)"
- Audio played on the soundboard will now appear as microphone input
Claude Code Prompt (paste this to start the build)
Build a Python soundboard desktop app for Windows 11 using the spec in soundboard_plan.md.
Start with this order:
1. requirements.txt
2. audio_engine.py — device listing, audio loading, play/stop functions
3. config.py — load/save JSON config
4. board.py — board state management (add, remove, rename buttons)
5. main.py — tkinter GUI that wires everything together
Requirements:
- Output device is selectable from a dropdown at the top (auto-selects VB-Cable if found)
- Buttons play sounds non-blocking using threads so the GUI doesn't freeze
- Right-click context menu on each button: Rename, Remove
- "Add Sound" button opens a file picker (wav, mp3, ogg, flac)
- Global volume slider 0-100%
- "Stop All" button
- All state saved/loaded from config.json automatically
- No third-party GUI frameworks — tkinter only
Stretch Goals (v2)
- Hotkey support (bind sounds to keyboard shortcuts)
- Per-button color customization
- Drag-to-reorder buttons
- Loop toggle per button
- Waveform preview thumbnail on each button
- System tray icon so the window can be minimized