Files
soundboard-py/soundboard_plan.md

210 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```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