Skip to content

Commit 3e10db3

Browse files
committed
feat: various new features
1 parent 14c8094 commit 3e10db3

15 files changed

Lines changed: 2365 additions & 61 deletions

README.md

Lines changed: 117 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ Play audio files through your microphone in multiplayer games like CS, Battlefie
55
## ✨ Features
66

77
- 🎵 **Play audio through your mic** - Route sound to your virtual microphone
8-
- ⌨️ **Hotkey support** - F1-F10 keys for instant sound playback
8+
- ⌨️ **Custom hotkeys** - Bind any key combination to any sound (F1-F12, Ctrl+, Alt+, etc.)
9+
- 🏷️ **Tags & categories** - Organize sounds with tags and filter by category
10+
-**Favorites** - Mark and quickly access your most-used sounds
11+
- 🔊 **Per-sound volume** - Set individual volume levels (0-200%) for each sound
12+
- 📋 **Queue & playlists** - Build queues and save them as reusable playlists
913
- 🎨 **Beautiful CLI** - Rich-click powered interface with colors and tables
1014
- 🎛️ **Multiple formats** - Supports WAV, MP3, OGG, FLAC, M4A
11-
- 🔊 **Auto-detection** - Finds VB-Cable and virtual audio devices automatically
15+
- 🔍 **Auto-detection** - Finds VB-Cable and virtual audio devices automatically
1216
- 📁 **Organized library** - Subdirectory support for sound organization
13-
- 🔉 **Volume control** - Adjustable playback volume with persistent settings
17+
- 🔉 **Global volume** - Adjustable playback volume with persistent settings
1418
- 🎲 **Auto-play mode** - Play all sounds randomly or sequentially
1519
- ⚙️ **Persistent config** - Saves your settings to `~/.muc/config.json`
1620
- 🎮 **Gaming ready** - Perfect for CS, Battlefield, COD, and more!
@@ -97,17 +101,51 @@ muc devices # List all audio devices
97101

98102
# Sound management
99103
muc sounds # List available sounds in your library
104+
muc sounds --tag meme # Filter by tag
105+
muc sounds --favorites # Show only favorites
100106
muc play [name] # Play a specific sound (prompts if no name)
101107
muc stop # Stop currently playing sound
102108
muc auto # Play all sounds randomly (use --sequential for alphabetical order)
109+
muc info [name] # Show detailed info about a sound
103110

104-
# Volume control
105-
muc volume # Show current volume
106-
muc volume 0.5 # Set volume to 50% (0.0 to 1.0)
111+
# Tags & organization
112+
muc tag airhorn meme loud # Add tags to a sound
113+
muc untag airhorn loud # Remove tags from a sound
114+
muc tags # List all tags with counts
115+
116+
# Favorites
117+
muc favorite airhorn # Toggle favorite status
118+
muc favorite airhorn --on # Add to favorites
119+
muc favorites # List all favorites
107120

108-
# Hotkey control
109-
muc hotkeys # Show hotkey bindings (F1-F10)
110-
muc listen # Start hotkey listener (press ESC to stop)
121+
# Volume control
122+
muc volume # Show current global volume
123+
muc volume 0.5 # Set global volume to 50% (0.0 to 1.0)
124+
muc sound-volume airhorn 1.5 # Set per-sound volume to 150% (0.0 to 2.0)
125+
126+
# Custom hotkeys
127+
muc bind f1 airhorn # Bind F1 to play airhorn
128+
muc bind "<ctrl>+a" applause # Bind Ctrl+A to applause
129+
muc unbind f1 # Remove hotkey binding
130+
muc unbind airhorn # Remove all bindings for a sound
131+
muc hotkeys # Show all hotkey bindings
132+
muc hotkeys-reset # Reset to default F1-F10 bindings
133+
muc listen # Start hotkey listener (press ESC to stop)
134+
135+
# Queue management
136+
muc queue add airhorn explosion # Add sounds to queue
137+
muc queue show # Show current queue
138+
muc queue play # Play queue sequentially
139+
muc queue skip # Skip to next sound
140+
muc queue shuffle # Shuffle the queue
141+
muc queue clear # Clear the queue
142+
143+
# Playlists
144+
muc playlist save mylist # Save current queue as playlist
145+
muc playlist load mylist # Load playlist into queue
146+
muc playlist list # List all saved playlists
147+
muc playlist show mylist # Show playlist contents
148+
muc playlist delete mylist # Delete a playlist
111149

112150
# Interactive mode
113151
muc interactive # Launch full interactive menu
@@ -142,17 +180,30 @@ muc --help # Show all commands
142180

143181
### Hotkey Bindings
144182

145-
The first 10 sounds (alphabetically) are automatically mapped to:
146-
- **F1** → First sound
147-
- **F2** → Second sound
148-
- **F3** → Third sound
149-
- ... through **F10**
183+
By default, the first 10 sounds (alphabetically) are mapped to F1-F10.
184+
185+
**Custom hotkeys** let you bind any key combination:
186+
```bash
187+
muc bind f1 airhorn # Simple key
188+
muc bind "<ctrl>+<shift>+a" boom # Modifier keys
189+
muc bind "<alt>+1" explosion # Alt + number
190+
```
191+
192+
**Hotkey modes** (set in config):
193+
- `default` - Only use auto-assigned F1-F10
194+
- `custom` - Only use your custom bindings
195+
- `merged` - Use both (default)
150196

151197
View bindings:
152198
```bash
153199
muc hotkeys
154200
```
155201

202+
Reset to defaults:
203+
```bash
204+
muc hotkeys-reset
205+
```
206+
156207
### Interactive Menu Mode
157208

158209
For a full-featured text menu:
@@ -225,11 +276,20 @@ Configuration is automatically saved to `~/.muc/config.json`:
225276
{
226277
"output_device_id": 6,
227278
"sounds_dir": "C:/path/to/muc/sounds",
228-
"volume": 1.0
279+
"volume": 1.0,
280+
"hotkeys": {
281+
"<f1>": "airhorn",
282+
"<ctrl>+<shift>+a": "applause"
283+
},
284+
"hotkey_mode": "merged"
229285
}
230286
```
231287

232-
You can manually edit this file or reconfigure using:
288+
**Additional data files:**
289+
- `~/.muc/metadata.json` - Sound tags, favorites, volumes, play counts
290+
- `~/.muc/playlists.json` - Saved playlists
291+
292+
You can manually edit these files or use CLI commands:
233293
```bash
234294
muc setup
235295
```
@@ -327,36 +387,43 @@ mkdir ~\.muc
327387
This project implements a clean software architecture:
328388

329389
```
330-
┌─────────────┐
331-
│ CLI Layer │ ← Rich-click commands (cli.py)
332-
└──────┬──────┘
333-
334-
┌──────▼──────┐
335-
│ Config │ ← Settings management (config.py)
336-
└──────┬──────┘
337-
338-
┌──────▼──────────────┬──────────────┐
339-
│ Soundboard │ Audio Manager│
340-
│ (hotkeys, sounds) │ (devices, io)│
341-
└─────────────────────┴──────────────┘
342-
│ │
343-
└──────┬─────────────┘
344-
345-
┌────────────────┐
346-
│ Audio Drivers │
347-
│ (sounddevice) │
348-
└───────┬────────┘
349-
350-
Virtual Audio Device
351-
(VB-Cable, etc.)
390+
┌─────────────────────────────────────────────────────┐
391+
│ CLI Layer │
392+
│ Rich-click commands (cli.py) │
393+
└───────────────────────┬─────────────────────────────┘
394+
395+
┌───────────────┼───────────────┐
396+
│ │ │
397+
┌───────▼───────┐ ┌─────▼─────┐ ┌───────▼───────┐
398+
│ Config │ │ Metadata │ │ Queue Manager │
399+
│ (settings) │ │(tags,favs)│ │ (playlists) │
400+
└───────┬───────┘ └─────┬─────┘ └───────┬───────┘
401+
│ │ │
402+
└───────────────┼───────────────┘
403+
404+
┌───────────────────────▼─────────────────────────────┐
405+
│ Soundboard │
406+
│ (sound library, hotkey manager, playback) │
407+
└───────────────────────┬─────────────────────────────┘
408+
409+
┌───────────────────────▼─────────────────────────────┐
410+
│ Audio Manager │
411+
│ (devices, volume, playback) │
412+
└───────────────────────┬─────────────────────────────┘
413+
414+
┌───────────────────────▼─────────────────────────────┐
415+
│ Virtual Audio Device │
416+
│ (VB-Cable, etc.) │
417+
└─────────────────────────────────────────────────────┘
352418
```
353419

354420
**Flow**:
355421
1. User runs command → CLI parses → Config loads settings
356-
2. Soundboard scans sounds → Sets up hotkeys
357-
3. User presses F1 → Hotkey handler triggered
358-
4. Audio Manager loads file → Outputs to virtual device
359-
5. Game reads from virtual device → Teammates hear sound
422+
2. Soundboard scans sounds → Loads metadata (tags, volumes)
423+
3. HotkeyManager sets up bindings (default + custom)
424+
4. User presses hotkey → Handler triggered → Metadata volume applied
425+
5. Audio Manager loads file → Outputs to virtual device
426+
6. Game reads from virtual device → Teammates hear sound
360427

361428
## 📁 Project Structure
362429

@@ -368,12 +435,18 @@ muc/
368435
│ ├── config.py # Configuration management
369436
│ ├── audio_manager.py # Audio device & playback
370437
│ ├── soundboard.py # Sound library & hotkeys
371-
│ └── main.py # Entry point
438+
│ ├── metadata.py # Tags, favorites, per-sound volume
439+
│ ├── hotkey_manager.py # Custom hotkey bindings
440+
│ ├── queue_manager.py # Queue & playlist management
441+
│ ├── validators.py # Input validation
442+
│ ├── exceptions.py # Custom exceptions
443+
│ └── logging_config.py # Logging setup
372444
├── sounds/ # Audio files directory
373445
│ └── README.md # Sound library guide
446+
├── tests/ # Unit & integration tests
374447
├── pyproject.toml # Project metadata & dependencies
375448
├── README.md # This file
376-
└── idea.md # Original design document
449+
└── Makefile # Development commands
377450
```
378451

379452
## 🤝 Contributing

pyproject.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,20 @@ muc = "src.cli:main"
3939

4040
[tool.ruff]
4141
line-length = 120
42-
lint.ignore = ["S101", "PLR6301", "PLR2004", "SLF001", "G004", "TRY003", "EM102", "EM101", "C901", "PLR0911"]
42+
lint.ignore = [
43+
"S101",
44+
"PLR6301",
45+
"PLR2004",
46+
"SLF001",
47+
"G004",
48+
"TRY003",
49+
"EM102",
50+
"EM101",
51+
"C901",
52+
"PLR0911",
53+
"PLR0914",
54+
"PLR0912",
55+
]
4356

4457
[tool.pytest.ini_options]
4558
addopts = ["-v", "--tb=short", "--strict-markers", "-ra"]

src/audio_manager.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,13 @@ def _adjust_channels(data: np.ndarray, max_channels: int) -> np.ndarray:
163163

164164
return data
165165

166-
def play_audio(self, audio_file: Path, *, blocking: bool = False) -> bool:
166+
def play_audio(self, audio_file: Path, *, blocking: bool = False, sound_volume: float = 1.0) -> bool:
167167
"""Play an audio file through the selected output device.
168168
169169
Args:
170170
audio_file: Path to the audio file
171171
blocking: If True, wait for playback to finish
172+
sound_volume: Per-sound volume multiplier (0.0 to 2.0), combined with global volume
172173
173174
Returns:
174175
True if playback started successfully, False otherwise.
@@ -220,8 +221,9 @@ def play_audio(self, audio_file: Path, *, blocking: bool = False) -> bool:
220221
# Adjust channels if needed
221222
data = self._adjust_channels(data, max_channels)
222223

223-
# Apply volume scaling
224-
data *= self.volume
224+
# Apply combined volume scaling: global x sound-specific
225+
final_volume = self.volume * sound_volume
226+
data *= final_volume
225227

226228
logger.debug(f"Starting playback to device {self.output_device_id}")
227229
sd.play(data, samplerate, device=self.output_device_id)

0 commit comments

Comments
 (0)