# 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.json` and 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 - 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.json` automatically ### 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) ```json { "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) ```python 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.Thread` per 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.ttk` for slightly nicer widgets - Grid of `tk.Button` widgets built dynamically from config - `tkinter.filedialog.askopenfilename()` for file picker - `tkinter.simpledialog.askstring()` for rename - `tkinter.Menu` for right-click context menus --- ## VB-Cable Setup Instructions (include in README) 1. Download and install VB-Cable from https://vb-audio.com/Cable 2. Reboot after installation 3. In the soundboard, select **"CABLE Input (VB-Audio Virtual Cable)"** as the output device 4. In Discord / OBS / Teams, set the microphone input to **"CABLE Output (VB-Audio Virtual Cable)"** 5. 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