diff --git a/CLAUDE.md b/CLAUDE.md index 96b5eaf..d26a3cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -249,8 +249,8 @@ Timeline Item: "Alice posted: Hello world (3 replies, collapsed)" - simpleaudio for cross-platform WAV playback with volume control - Subprocess fallback (sox/play on Linux, afplay on macOS, PowerShell on Windows) - Fallback to default pack if sound missing -- Master volume and notification volume controls -- Enable/disable per event +- Single volume control for all sound pack audio +- "None" sound pack option to disable all sounds - Pack discovery and validation - 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 username = user timeline_refresh_interval = 60 +auto_refresh_enabled = true [audio] -sound_pack = Doom -master_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 +sound_pack = default +volume = 100 [notifications] enabled = true @@ -387,7 +380,6 @@ mentions = true boosts = false favorites = false follows = true -timeline_updates = false [timeline] posts_per_page = 40 diff --git a/README.md b/README.md index 697fe0c..3fc3f19 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Bifrost includes a sophisticated sound system with: - **One-click Installation**: Download, validate, and install soundpacks securely - **Customizable Sound Packs**: Themed audio notifications (Default pack included) - **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 ## Compose Features diff --git a/src/audio/sound_manager.py b/src/audio/sound_manager.py index 05f3517..88247f6 100644 --- a/src/audio/sound_manager.py +++ b/src/audio/sound_manager.py @@ -178,6 +178,11 @@ class SoundManager: """Load the currently selected sound pack""" 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: self.current_pack = self.sound_packs[current_pack_name] else: @@ -189,7 +194,12 @@ class SoundManager: def set_current_pack(self, pack_name: str) -> bool: """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.settings.set('audio', 'sound_pack', pack_name) self.settings.save_settings() @@ -198,7 +208,8 @@ class SoundManager: def get_available_packs(self) -> List[str]: """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]: """Get information about a sound pack""" @@ -214,13 +225,13 @@ class SoundManager: def is_sound_enabled(self, event_type: str) -> bool: """Check if sound is enabled for an event type""" - if not self.settings.get_bool('sounds', 'enabled', True): - return False - return self.settings.get_bool('sounds', f'{event_type}_enabled', True) + # Sounds are enabled if we have a sound pack loaded (not None) + return self.current_pack is not None def get_sound_volume(self, event_type: str) -> float: """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): """Play a sound file using simpleaudio or platform-specific fallback""" @@ -228,9 +239,8 @@ class SoundManager: return try: - # Calculate final volume - master_vol = int(self.settings.get('audio', 'master_volume', 100) or 100) / 100.0 - final_volume = master_vol * volume_multiplier + # Use the volume multiplier as final volume (already includes sound pack volume) + final_volume = volume_multiplier if SIMPLEAUDIO_AVAILABLE: # 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) if sound_path: - # Get volume settings for this event type - notification_vol = int(self.settings.get('audio', 'notification_volume', 100) or 100) / 100.0 - volume_multiplier = notification_vol # Use system volume by default - self.play_sound(sound_path, volume_multiplier) + # Get volume settings + notification_vol = self.get_sound_volume(event_type) + self.play_sound(sound_path, notification_vol) else: # Try fallback to default pack default_pack = self.sound_packs.get('default') diff --git a/src/config/settings.py b/src/config/settings.py index 12f353f..3f158b2 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -59,27 +59,13 @@ class SettingsManager: self.config.add_section('general') self.config.set('general', 'instance_url', '') 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', 'auto_refresh_enabled', 'true') - # Sound settings - self.config.add_section('sounds') - self.config.set('sounds', 'enabled', 'true') - self.config.set('sounds', 'private_message_enabled', 'true') - 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') + # Audio settings + self.config.add_section('audio') + self.config.set('audio', 'sound_pack', 'default') + self.config.set('audio', 'volume', '100') # Accessibility settings self.config.add_section('accessibility') diff --git a/src/widgets/settings_dialog.py b/src/widgets/settings_dialog.py index 0d6cd05..f74ad0c 100644 --- a/src/widgets/settings_dialog.py +++ b/src/widgets/settings_dialog.py @@ -78,42 +78,19 @@ class SettingsDialog(QDialog): volume_group = QGroupBox("Volume Settings") volume_layout = QFormLayout(volume_group) - self.master_volume = QSpinBox() - self.master_volume.setRange(0, 100) - self.master_volume.setSuffix("%") - self.master_volume.setAccessibleName("Master Volume") - self.master_volume.setAccessibleDescription("Overall volume for all sounds") - volume_layout.addRow("Master Volume:", self.master_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) + self.sound_pack_volume = QSpinBox() + self.sound_pack_volume.setRange(0, 100) + self.sound_pack_volume.setSuffix("%") + self.sound_pack_volume.setAccessibleName("Sound Pack Volume") + self.sound_pack_volume.setAccessibleDescription("Volume for all sounds from the selected sound pack") + volume_layout.addRow("Sound Pack Volume:", self.sound_pack_volume) layout.addWidget(volume_group) - # Audio enable/disable options - options_group = QGroupBox("Audio Options") - options_layout = QVBoxLayout(options_group) - - 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) + # Note about sound pack selection + note_label = QLabel("To disable sounds, select 'None' as the sound pack above.") + note_label.setStyleSheet("font-style: italic; color: #666;") + layout.addWidget(note_label) layout.addStretch() self.tabs.addTab(audio_widget, "&Audio") @@ -261,26 +238,22 @@ class SettingsDialog(QDialog): if index >= 0: self.sound_pack_combo.setCurrentIndex(index) - self.master_volume.setValue(int(self.settings.get('audio', 'master_volume', 100) or 100)) - self.notification_volume.setValue(int(self.settings.get('audio', 'notification_volume', 100) or 100)) + self.sound_pack_volume.setValue(int(self.settings.get('audio', '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) - self.enable_desktop_notifications.setChecked(bool(self.settings.get('notifications', 'enabled', True))) - self.notify_direct_messages.setChecked(bool(self.settings.get('notifications', 'direct_messages', True))) - self.notify_mentions.setChecked(bool(self.settings.get('notifications', 'mentions', True))) - self.notify_boosts.setChecked(bool(self.settings.get('notifications', 'boosts', False))) - self.notify_favorites.setChecked(bool(self.settings.get('notifications', 'favorites', False))) - self.notify_follows.setChecked(bool(self.settings.get('notifications', 'follows', True))) - self.notify_timeline_updates.setChecked(bool(self.settings.get('notifications', 'timeline_updates', False))) + self.enable_desktop_notifications.setChecked(self.settings.get_bool('notifications', 'enabled', True)) + self.notify_direct_messages.setChecked(self.settings.get_bool('notifications', 'direct_messages', True)) + self.notify_mentions.setChecked(self.settings.get_bool('notifications', 'mentions', True)) + self.notify_boosts.setChecked(self.settings.get_bool('notifications', 'boosts', False)) + self.notify_favorites.setChecked(self.settings.get_bool('notifications', 'favorites', False)) + self.notify_follows.setChecked(self.settings.get_bool('notifications', 'follows', True)) + self.notify_timeline_updates.setChecked(self.settings.get_bool('notifications', 'timeline_updates', False)) # Accessibility settings 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.announce_thread_state.setChecked(bool(self.settings.get('accessibility', 'announce_thread_state', True))) + self.verbose_announcements.setChecked(self.settings.get_bool('accessibility', 'verbose_announcements', True)) + self.announce_thread_state.setChecked(self.settings.get_bool('accessibility', 'announce_thread_state', True)) # Timeline settings 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 selected_pack = self.sound_pack_combo.currentData() self.settings.set('audio', 'sound_pack', selected_pack) - self.settings.set('audio', 'master_volume', self.master_volume.value()) - self.settings.set('audio', 'notification_volume', self.notification_volume.value()) + self.settings.set('audio', 'volume', self.sound_pack_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 self.settings.set('notifications', 'enabled', self.enable_desktop_notifications.isChecked())