Simplify sound system with pack-based approach
- Remove complex per-event sound enable/disable settings - Replace dual volume controls with single "Sound Pack Volume" - Add "None" sound pack option to disable all sounds - Fix boolean settings parsing in settings dialog - Update documentation to reflect simplified sound system Sound system is now intuitive: select a sound pack (or "None") and set one volume level. Much cleaner than the previous per-event checkboxes and confusing dual volume controls. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
18
CLAUDE.md
18
CLAUDE.md
@ -249,8 +249,8 @@ Timeline Item: "Alice posted: Hello world (3 replies, collapsed)"
|
|||||||
- simpleaudio for cross-platform WAV playback with volume control
|
- simpleaudio for cross-platform WAV playback with volume control
|
||||||
- Subprocess fallback (sox/play on Linux, afplay on macOS, PowerShell on Windows)
|
- Subprocess fallback (sox/play on Linux, afplay on macOS, PowerShell on Windows)
|
||||||
- Fallback to default pack if sound missing
|
- Fallback to default pack if sound missing
|
||||||
- Master volume and notification volume controls
|
- Single volume control for all sound pack audio
|
||||||
- Enable/disable per event
|
- "None" sound pack option to disable all sounds
|
||||||
- Pack discovery and validation
|
- Pack discovery and validation
|
||||||
- Smart threading (direct calls for simpleaudio, threaded for subprocess)
|
- Smart threading (direct calls for simpleaudio, threaded for subprocess)
|
||||||
|
|
||||||
@ -367,18 +367,11 @@ Timeline Item: "Alice posted: Hello world (3 replies, collapsed)"
|
|||||||
instance_url = https://example.social
|
instance_url = https://example.social
|
||||||
username = user
|
username = user
|
||||||
timeline_refresh_interval = 60
|
timeline_refresh_interval = 60
|
||||||
|
auto_refresh_enabled = true
|
||||||
|
|
||||||
[audio]
|
[audio]
|
||||||
sound_pack = Doom
|
sound_pack = default
|
||||||
master_volume = 100
|
volume = 100
|
||||||
notification_volume = 100
|
|
||||||
|
|
||||||
[sounds]
|
|
||||||
private_message_enabled = true
|
|
||||||
private_message_volume = 0.8
|
|
||||||
mention_enabled = true
|
|
||||||
mention_volume = 1.0
|
|
||||||
# ... other sound settings
|
|
||||||
|
|
||||||
[notifications]
|
[notifications]
|
||||||
enabled = true
|
enabled = true
|
||||||
@ -387,7 +380,6 @@ mentions = true
|
|||||||
boosts = false
|
boosts = false
|
||||||
favorites = false
|
favorites = false
|
||||||
follows = true
|
follows = true
|
||||||
timeline_updates = false
|
|
||||||
|
|
||||||
[timeline]
|
[timeline]
|
||||||
posts_per_page = 40
|
posts_per_page = 40
|
||||||
|
@ -34,7 +34,8 @@ Bifrost includes a sophisticated sound system with:
|
|||||||
- **One-click Installation**: Download, validate, and install soundpacks securely
|
- **One-click Installation**: Download, validate, and install soundpacks securely
|
||||||
- **Customizable Sound Packs**: Themed audio notifications (Default pack included)
|
- **Customizable Sound Packs**: Themed audio notifications (Default pack included)
|
||||||
- **Audio Feedback**: Sound events for all major actions and notifications
|
- **Audio Feedback**: Sound events for all major actions and notifications
|
||||||
- **Per-event Volume Control**: Fine-tune individual sound effects
|
- **Simple Volume Control**: Single volume setting for all sound pack audio
|
||||||
|
- **Easy Disable**: Select "None" sound pack to disable all sounds
|
||||||
- **Cross-platform Audio Support**: Works on Linux, Windows, and macOS
|
- **Cross-platform Audio Support**: Works on Linux, Windows, and macOS
|
||||||
|
|
||||||
## Compose Features
|
## Compose Features
|
||||||
|
@ -178,6 +178,11 @@ class SoundManager:
|
|||||||
"""Load the currently selected sound pack"""
|
"""Load the currently selected sound pack"""
|
||||||
current_pack_name = self.settings.get('audio', 'sound_pack', 'default')
|
current_pack_name = self.settings.get('audio', 'sound_pack', 'default')
|
||||||
|
|
||||||
|
# Handle 'None' pack (no sounds)
|
||||||
|
if current_pack_name.lower() == 'none':
|
||||||
|
self.current_pack = None
|
||||||
|
return
|
||||||
|
|
||||||
if current_pack_name in self.sound_packs:
|
if current_pack_name in self.sound_packs:
|
||||||
self.current_pack = self.sound_packs[current_pack_name]
|
self.current_pack = self.sound_packs[current_pack_name]
|
||||||
else:
|
else:
|
||||||
@ -189,7 +194,12 @@ class SoundManager:
|
|||||||
|
|
||||||
def set_current_pack(self, pack_name: str) -> bool:
|
def set_current_pack(self, pack_name: str) -> bool:
|
||||||
"""Set the current sound pack"""
|
"""Set the current sound pack"""
|
||||||
if pack_name in self.sound_packs:
|
if pack_name.lower() == 'none':
|
||||||
|
self.current_pack = None
|
||||||
|
self.settings.set('audio', 'sound_pack', 'None')
|
||||||
|
self.settings.save_settings()
|
||||||
|
return True
|
||||||
|
elif pack_name in self.sound_packs:
|
||||||
self.current_pack = self.sound_packs[pack_name]
|
self.current_pack = self.sound_packs[pack_name]
|
||||||
self.settings.set('audio', 'sound_pack', pack_name)
|
self.settings.set('audio', 'sound_pack', pack_name)
|
||||||
self.settings.save_settings()
|
self.settings.save_settings()
|
||||||
@ -198,7 +208,8 @@ class SoundManager:
|
|||||||
|
|
||||||
def get_available_packs(self) -> List[str]:
|
def get_available_packs(self) -> List[str]:
|
||||||
"""Get list of available sound pack names"""
|
"""Get list of available sound pack names"""
|
||||||
return list(self.sound_packs.keys())
|
packs = ['None'] + list(self.sound_packs.keys())
|
||||||
|
return packs
|
||||||
|
|
||||||
def get_pack_info(self, pack_name: str) -> Optional[Dict]:
|
def get_pack_info(self, pack_name: str) -> Optional[Dict]:
|
||||||
"""Get information about a sound pack"""
|
"""Get information about a sound pack"""
|
||||||
@ -214,13 +225,13 @@ class SoundManager:
|
|||||||
|
|
||||||
def is_sound_enabled(self, event_type: str) -> bool:
|
def is_sound_enabled(self, event_type: str) -> bool:
|
||||||
"""Check if sound is enabled for an event type"""
|
"""Check if sound is enabled for an event type"""
|
||||||
if not self.settings.get_bool('sounds', 'enabled', True):
|
# Sounds are enabled if we have a sound pack loaded (not None)
|
||||||
return False
|
return self.current_pack is not None
|
||||||
return self.settings.get_bool('sounds', f'{event_type}_enabled', True)
|
|
||||||
|
|
||||||
def get_sound_volume(self, event_type: str) -> float:
|
def get_sound_volume(self, event_type: str) -> float:
|
||||||
"""Get volume setting for an event type"""
|
"""Get volume setting for an event type"""
|
||||||
return self.settings.get_float('sounds', f'{event_type}_volume', 0.8)
|
# Use sound pack volume for all sounds
|
||||||
|
return self.settings.get_float('audio', 'volume', 100) / 100.0
|
||||||
|
|
||||||
def play_sound(self, file_path: Path, volume_multiplier: float = 1.0):
|
def play_sound(self, file_path: Path, volume_multiplier: float = 1.0):
|
||||||
"""Play a sound file using simpleaudio or platform-specific fallback"""
|
"""Play a sound file using simpleaudio or platform-specific fallback"""
|
||||||
@ -228,9 +239,8 @@ class SoundManager:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Calculate final volume
|
# Use the volume multiplier as final volume (already includes sound pack volume)
|
||||||
master_vol = int(self.settings.get('audio', 'master_volume', 100) or 100) / 100.0
|
final_volume = volume_multiplier
|
||||||
final_volume = master_vol * volume_multiplier
|
|
||||||
|
|
||||||
if SIMPLEAUDIO_AVAILABLE:
|
if SIMPLEAUDIO_AVAILABLE:
|
||||||
# Use simpleaudio directly (no threading needed for Python library)
|
# Use simpleaudio directly (no threading needed for Python library)
|
||||||
@ -342,10 +352,9 @@ class SoundManager:
|
|||||||
|
|
||||||
sound_path = self.current_pack.get_sound_path(event_type)
|
sound_path = self.current_pack.get_sound_path(event_type)
|
||||||
if sound_path:
|
if sound_path:
|
||||||
# Get volume settings for this event type
|
# Get volume settings
|
||||||
notification_vol = int(self.settings.get('audio', 'notification_volume', 100) or 100) / 100.0
|
notification_vol = self.get_sound_volume(event_type)
|
||||||
volume_multiplier = notification_vol # Use system volume by default
|
self.play_sound(sound_path, notification_vol)
|
||||||
self.play_sound(sound_path, volume_multiplier)
|
|
||||||
else:
|
else:
|
||||||
# Try fallback to default pack
|
# Try fallback to default pack
|
||||||
default_pack = self.sound_packs.get('default')
|
default_pack = self.sound_packs.get('default')
|
||||||
|
@ -59,27 +59,13 @@ class SettingsManager:
|
|||||||
self.config.add_section('general')
|
self.config.add_section('general')
|
||||||
self.config.set('general', 'instance_url', '')
|
self.config.set('general', 'instance_url', '')
|
||||||
self.config.set('general', 'username', '')
|
self.config.set('general', 'username', '')
|
||||||
self.config.set('general', 'current_sound_pack', 'default')
|
|
||||||
self.config.set('general', 'timeline_refresh_interval', '300')
|
self.config.set('general', 'timeline_refresh_interval', '300')
|
||||||
self.config.set('general', 'auto_refresh_enabled', 'true')
|
self.config.set('general', 'auto_refresh_enabled', 'true')
|
||||||
|
|
||||||
# Sound settings
|
# Audio settings
|
||||||
self.config.add_section('sounds')
|
self.config.add_section('audio')
|
||||||
self.config.set('sounds', 'enabled', 'true')
|
self.config.set('audio', 'sound_pack', 'default')
|
||||||
self.config.set('sounds', 'private_message_enabled', 'true')
|
self.config.set('audio', 'volume', '100')
|
||||||
self.config.set('sounds', 'private_message_volume', '0.8')
|
|
||||||
self.config.set('sounds', 'mention_enabled', 'true')
|
|
||||||
self.config.set('sounds', 'mention_volume', '1.0')
|
|
||||||
self.config.set('sounds', 'boost_enabled', 'true')
|
|
||||||
self.config.set('sounds', 'boost_volume', '0.7')
|
|
||||||
self.config.set('sounds', 'reply_enabled', 'true')
|
|
||||||
self.config.set('sounds', 'reply_volume', '0.8')
|
|
||||||
self.config.set('sounds', 'post_sent_enabled', 'true')
|
|
||||||
self.config.set('sounds', 'post_sent_volume', '0.9')
|
|
||||||
self.config.set('sounds', 'timeline_update_enabled', 'true')
|
|
||||||
self.config.set('sounds', 'timeline_update_volume', '0.5')
|
|
||||||
self.config.set('sounds', 'notification_enabled', 'true')
|
|
||||||
self.config.set('sounds', 'notification_volume', '0.8')
|
|
||||||
|
|
||||||
# Accessibility settings
|
# Accessibility settings
|
||||||
self.config.add_section('accessibility')
|
self.config.add_section('accessibility')
|
||||||
|
@ -78,42 +78,19 @@ class SettingsDialog(QDialog):
|
|||||||
volume_group = QGroupBox("Volume Settings")
|
volume_group = QGroupBox("Volume Settings")
|
||||||
volume_layout = QFormLayout(volume_group)
|
volume_layout = QFormLayout(volume_group)
|
||||||
|
|
||||||
self.master_volume = QSpinBox()
|
self.sound_pack_volume = QSpinBox()
|
||||||
self.master_volume.setRange(0, 100)
|
self.sound_pack_volume.setRange(0, 100)
|
||||||
self.master_volume.setSuffix("%")
|
self.sound_pack_volume.setSuffix("%")
|
||||||
self.master_volume.setAccessibleName("Master Volume")
|
self.sound_pack_volume.setAccessibleName("Sound Pack Volume")
|
||||||
self.master_volume.setAccessibleDescription("Overall volume for all sounds")
|
self.sound_pack_volume.setAccessibleDescription("Volume for all sounds from the selected sound pack")
|
||||||
volume_layout.addRow("Master Volume:", self.master_volume)
|
volume_layout.addRow("Sound Pack Volume:", self.sound_pack_volume)
|
||||||
|
|
||||||
self.notification_volume = QSpinBox()
|
|
||||||
self.notification_volume.setRange(0, 100)
|
|
||||||
self.notification_volume.setSuffix("%")
|
|
||||||
self.notification_volume.setAccessibleName("Notification Volume")
|
|
||||||
self.notification_volume.setAccessibleDescription("Volume for notification sounds")
|
|
||||||
volume_layout.addRow("Notification Volume:", self.notification_volume)
|
|
||||||
|
|
||||||
layout.addWidget(volume_group)
|
layout.addWidget(volume_group)
|
||||||
|
|
||||||
# Audio enable/disable options
|
# Note about sound pack selection
|
||||||
options_group = QGroupBox("Audio Options")
|
note_label = QLabel("To disable sounds, select 'None' as the sound pack above.")
|
||||||
options_layout = QVBoxLayout(options_group)
|
note_label.setStyleSheet("font-style: italic; color: #666;")
|
||||||
|
layout.addWidget(note_label)
|
||||||
self.enable_sounds = QCheckBox("Enable sound effects")
|
|
||||||
self.enable_sounds.setAccessibleName("Enable Sound Effects")
|
|
||||||
self.enable_sounds.setAccessibleDescription("Turn sound effects on or off globally")
|
|
||||||
options_layout.addWidget(self.enable_sounds)
|
|
||||||
|
|
||||||
self.enable_post_sounds = QCheckBox("Play sounds for post actions")
|
|
||||||
self.enable_post_sounds.setAccessibleName("Post Action Sounds")
|
|
||||||
self.enable_post_sounds.setAccessibleDescription("Play sounds when posting, boosting, or favoriting")
|
|
||||||
options_layout.addWidget(self.enable_post_sounds)
|
|
||||||
|
|
||||||
self.enable_timeline_sounds = QCheckBox("Play sounds for timeline updates")
|
|
||||||
self.enable_timeline_sounds.setAccessibleName("Timeline Update Sounds")
|
|
||||||
self.enable_timeline_sounds.setAccessibleDescription("Play sounds when the timeline refreshes")
|
|
||||||
options_layout.addWidget(self.enable_timeline_sounds)
|
|
||||||
|
|
||||||
layout.addWidget(options_group)
|
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
|
|
||||||
self.tabs.addTab(audio_widget, "&Audio")
|
self.tabs.addTab(audio_widget, "&Audio")
|
||||||
@ -261,26 +238,22 @@ class SettingsDialog(QDialog):
|
|||||||
if index >= 0:
|
if index >= 0:
|
||||||
self.sound_pack_combo.setCurrentIndex(index)
|
self.sound_pack_combo.setCurrentIndex(index)
|
||||||
|
|
||||||
self.master_volume.setValue(int(self.settings.get('audio', 'master_volume', 100) or 100))
|
self.sound_pack_volume.setValue(int(self.settings.get('audio', 'volume', 100) or 100))
|
||||||
self.notification_volume.setValue(int(self.settings.get('audio', 'notification_volume', 100) or 100))
|
|
||||||
|
|
||||||
self.enable_sounds.setChecked(bool(self.settings.get('audio', 'enabled', True)))
|
|
||||||
self.enable_post_sounds.setChecked(bool(self.settings.get('audio', 'post_sounds', True)))
|
|
||||||
self.enable_timeline_sounds.setChecked(bool(self.settings.get('audio', 'timeline_sounds', True)))
|
|
||||||
|
|
||||||
# Desktop notification settings (defaults: DMs, mentions, follows ON; others OFF)
|
# Desktop notification settings (defaults: DMs, mentions, follows ON; others OFF)
|
||||||
self.enable_desktop_notifications.setChecked(bool(self.settings.get('notifications', 'enabled', True)))
|
self.enable_desktop_notifications.setChecked(self.settings.get_bool('notifications', 'enabled', True))
|
||||||
self.notify_direct_messages.setChecked(bool(self.settings.get('notifications', 'direct_messages', True)))
|
self.notify_direct_messages.setChecked(self.settings.get_bool('notifications', 'direct_messages', True))
|
||||||
self.notify_mentions.setChecked(bool(self.settings.get('notifications', 'mentions', True)))
|
self.notify_mentions.setChecked(self.settings.get_bool('notifications', 'mentions', True))
|
||||||
self.notify_boosts.setChecked(bool(self.settings.get('notifications', 'boosts', False)))
|
self.notify_boosts.setChecked(self.settings.get_bool('notifications', 'boosts', False))
|
||||||
self.notify_favorites.setChecked(bool(self.settings.get('notifications', 'favorites', False)))
|
self.notify_favorites.setChecked(self.settings.get_bool('notifications', 'favorites', False))
|
||||||
self.notify_follows.setChecked(bool(self.settings.get('notifications', 'follows', True)))
|
self.notify_follows.setChecked(self.settings.get_bool('notifications', 'follows', True))
|
||||||
self.notify_timeline_updates.setChecked(bool(self.settings.get('notifications', 'timeline_updates', False)))
|
self.notify_timeline_updates.setChecked(self.settings.get_bool('notifications', 'timeline_updates', False))
|
||||||
|
|
||||||
# Accessibility settings
|
# Accessibility settings
|
||||||
self.page_step_size.setValue(int(self.settings.get('accessibility', 'page_step_size', 5) or 5))
|
self.page_step_size.setValue(int(self.settings.get('accessibility', 'page_step_size', 5) or 5))
|
||||||
self.verbose_announcements.setChecked(bool(self.settings.get('accessibility', 'verbose_announcements', True)))
|
self.verbose_announcements.setChecked(self.settings.get_bool('accessibility', 'verbose_announcements', True))
|
||||||
self.announce_thread_state.setChecked(bool(self.settings.get('accessibility', 'announce_thread_state', True)))
|
self.announce_thread_state.setChecked(self.settings.get_bool('accessibility', 'announce_thread_state', True))
|
||||||
|
|
||||||
# Timeline settings
|
# Timeline settings
|
||||||
self.posts_per_page.setValue(int(self.settings.get('timeline', 'posts_per_page', 40) or 40))
|
self.posts_per_page.setValue(int(self.settings.get('timeline', 'posts_per_page', 40) or 40))
|
||||||
@ -290,12 +263,8 @@ class SettingsDialog(QDialog):
|
|||||||
# Audio settings
|
# Audio settings
|
||||||
selected_pack = self.sound_pack_combo.currentData()
|
selected_pack = self.sound_pack_combo.currentData()
|
||||||
self.settings.set('audio', 'sound_pack', selected_pack)
|
self.settings.set('audio', 'sound_pack', selected_pack)
|
||||||
self.settings.set('audio', 'master_volume', self.master_volume.value())
|
self.settings.set('audio', 'volume', self.sound_pack_volume.value())
|
||||||
self.settings.set('audio', 'notification_volume', self.notification_volume.value())
|
|
||||||
|
|
||||||
self.settings.set('audio', 'enabled', self.enable_sounds.isChecked())
|
|
||||||
self.settings.set('audio', 'post_sounds', self.enable_post_sounds.isChecked())
|
|
||||||
self.settings.set('audio', 'timeline_sounds', self.enable_timeline_sounds.isChecked())
|
|
||||||
|
|
||||||
# Desktop notification settings
|
# Desktop notification settings
|
||||||
self.settings.set('notifications', 'enabled', self.enable_desktop_notifications.isChecked())
|
self.settings.set('notifications', 'enabled', self.enable_desktop_notifications.isChecked())
|
||||||
|
Reference in New Issue
Block a user