diff --git a/CLAUDE.md b/CLAUDE.md index 06359d3..977d34a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,6 +3,10 @@ ## Project Overview Bifrost is a fully accessible fediverse client built with PySide6, designed specifically for screen reader users. The application uses "post/posts" terminology instead of "toot" and focuses on excellent keyboard navigation and audio feedback. +## Development Workflow +- Check for any changes in git project before doing anything else. Make sure the latest changes have been pulled +- See what has changed, use git commands and examine the code to make sure you are up to date with the latest code + ## Core Features - Full ActivityPub protocol support (Pleroma and GoToSocial primary targets) - Threaded conversation navigation with collapsible tree view @@ -137,6 +141,15 @@ Timeline Item: "Alice posted: Hello world (3 replies, collapsed)" - **collapse**: Thread collapsed - **success**: General success feedback - **error**: Error occurred +- **autocomplete**: Autocomplete suggestions available +- **autocomplete_end**: Autocomplete interaction ended + +### Sound System Policy +- **No Sound Generation**: The application should never generate sounds programmatically +- **Sound Pack Reliance**: All audio feedback must come from sound pack files +- **Default Pack Requirement**: A complete default sound pack must ship with the project +- **Themed Packs**: Users can install additional themed sound packs (like Doom) +- If someone requests sound generation, gently remind them that all sounds should be covered by sound packs ### Sound Pack Structure **pack.json Format:** diff --git a/src/audio/sound_manager.py b/src/audio/sound_manager.py index f01e1f6..66faa72 100644 --- a/src/audio/sound_manager.py +++ b/src/audio/sound_manager.py @@ -160,46 +160,6 @@ class SoundManager: with open(pack_file, 'w') as f: json.dump(pack_data, f, indent=2) - - # Create default autocomplete sound if it doesn't exist - autocomplete_sound = default_dir / "autocomplete.wav" - if not autocomplete_sound.exists(): - self.create_autocomplete_sound(autocomplete_sound) - - def create_autocomplete_sound(self, output_path: Path): - """Create a simple autocomplete notification sound""" - try: - # Parameters for a gentle notification chime - sample_rate = 22050 # Lower sample rate for smaller file - duration = 0.15 # Short 150ms sound - frequency = 800 # Pleasant mid-range frequency - - # Generate time array - t = np.linspace(0, duration, int(sample_rate * duration), False) - - # Create a gentle chime with fade in/out to avoid clicks - tone = np.sin(2 * np.pi * frequency * t) - - # Add slight fade in/out to prevent audio clicks - fade_samples = int(sample_rate * 0.02) # 20ms fade - tone[:fade_samples] *= np.linspace(0, 1, fade_samples) - tone[-fade_samples:] *= np.linspace(1, 0, fade_samples) - - # Reduce volume to be gentle - tone *= 0.3 - - # Convert to 16-bit PCM - audio = (tone * 32767).astype(np.int16) - - # Write WAV file - with wave.open(str(output_path), 'w') as wav_file: - wav_file.setnchannels(1) # Mono - wav_file.setsampwidth(2) # 16-bit - wav_file.setframerate(sample_rate) - wav_file.writeframes(audio.tobytes()) - - except Exception as e: - print(f"Failed to create autocomplete sound: {e}") def load_current_pack(self): """Load the currently selected sound pack""" @@ -438,6 +398,10 @@ class SoundManager: """Play autocomplete available sound""" self.play_event("autocomplete") + def play_autocomplete_end(self): + """Play autocomplete ended sound""" + self.play_event("autocomplete_end") + def test_sound(self, event_type: str): """Test play a specific sound type""" self.play_event(event_type) \ No newline at end of file diff --git a/src/widgets/autocomplete_textedit.py b/src/widgets/autocomplete_textedit.py index 1982da5..f99e50d 100644 --- a/src/widgets/autocomplete_textedit.py +++ b/src/widgets/autocomplete_textedit.py @@ -22,6 +22,9 @@ class AutocompleteTextEdit(QTextEdit): # Sound manager for audio feedback self.sound_manager = sound_manager + # Autocomplete sound state tracking + self.autocomplete_sound_played = False + # Lists for autocomplete self.mention_list = [] # Will be populated from followers/following self.emoji_list = [] # Will be populated from instance custom emojis @@ -257,19 +260,26 @@ class AutocompleteTextEdit(QTextEdit): popup = self.completer.popup() popup.setAccessibleName(f"{completion_type.title()} Autocomplete") - # Play autocomplete available sound - if self.sound_manager: + # Play autocomplete available sound (only once per session) + if self.sound_manager and not self.autocomplete_sound_played: self.sound_manager.play_autocomplete() + self.autocomplete_sound_played = True def hide_completer(self): """Hide the completer""" if self.completer: popup = self.completer.popup() + if popup.isVisible(): # Only play sound if completer was actually visible + # Play autocomplete end sound (only if autocomplete sound was played) + if self.sound_manager and self.autocomplete_sound_played: + self.sound_manager.play_autocomplete_end() popup.hide() # Clear the completion state self.completion_prefix = "" self.completion_start = 0 self.completion_type = None + # Reset sound state for next autocomplete session + self.autocomplete_sound_played = False def insert_completion(self): """Insert the selected completion"""