round from from Claude Opus 4.8
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user