Files
soundboard-py/soundboard_plan.md
T

7.0 KiB
Raw Blame History

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 (0100%) 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.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