From 2e0c7263b4d5283821c7ace3f656a3b6f674fcf8 Mon Sep 17 00:00:00 2001 From: Hunter Jozwiak Date: Sun, 20 Jul 2025 20:53:18 -0400 Subject: [PATCH] Add audio feedback for autocomplete suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implemented autocomplete sound notification system for accessibility - Added gentle 150ms chime (800Hz) when autocomplete suggestions appear - Enhanced AutocompleteTextEdit to accept sound_manager parameter - Created automatic sound generation for default pack (autocomplete.wav) - Added play_autocomplete() method to SoundManager with 30% volume - Sound includes 20ms fade in/out to prevent audio clicks - Provides valuable audio feedback for screen reader users 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/audio/sound_manager.py | 47 +++++++++++++++++++++++++++- src/widgets/autocomplete_textedit.py | 11 +++++-- src/widgets/compose_dialog.py | 2 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/audio/sound_manager.py b/src/audio/sound_manager.py index 0e18ff9..f01e1f6 100644 --- a/src/audio/sound_manager.py +++ b/src/audio/sound_manager.py @@ -153,12 +153,53 @@ class SoundManager: "success": "success.wav", "error": "error.wav", "expand": "expand.wav", - "collapse": "collapse.wav" + "collapse": "collapse.wav", + "autocomplete": "autocomplete.wav" } } 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""" @@ -393,6 +434,10 @@ class SoundManager: """Play thread collapse sound""" self.play_event("collapse") + def play_autocomplete(self): + """Play autocomplete available sound""" + self.play_event("autocomplete") + 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 ea5c01c..1982da5 100644 --- a/src/widgets/autocomplete_textedit.py +++ b/src/widgets/autocomplete_textedit.py @@ -16,9 +16,12 @@ class AutocompleteTextEdit(QTextEdit): mention_requested = Signal(str) # Emitted when user types @ to request user list emoji_requested = Signal(str) # Emitted when user types : to request emoji list - def __init__(self, parent=None): + def __init__(self, parent=None, sound_manager=None): super().__init__(parent) + # Sound manager for audio feedback + self.sound_manager = sound_manager + # Lists for autocomplete self.mention_list = [] # Will be populated from followers/following self.emoji_list = [] # Will be populated from instance custom emojis @@ -199,8 +202,6 @@ class AutocompleteTextEdit(QTextEdit): match_full = name_lower.startswith(prefix_lower) if match_username or match_full: matches.append(name) - else: - if matches: self.show_completer(matches, prefix, start_pos, 'mention') @@ -256,6 +257,10 @@ class AutocompleteTextEdit(QTextEdit): popup = self.completer.popup() popup.setAccessibleName(f"{completion_type.title()} Autocomplete") + # Play autocomplete available sound + if self.sound_manager: + self.sound_manager.play_autocomplete() + def hide_completer(self): """Hide the completer""" if self.completer: diff --git a/src/widgets/compose_dialog.py b/src/widgets/compose_dialog.py index 07efc53..2e6a533 100644 --- a/src/widgets/compose_dialog.py +++ b/src/widgets/compose_dialog.py @@ -74,7 +74,7 @@ class ComposeDialog(QDialog): layout.addWidget(self.char_count_label) # Main text area with autocomplete - self.text_edit = AutocompleteTextEdit() + self.text_edit = AutocompleteTextEdit(sound_manager=self.sound_manager) self.text_edit.setAccessibleName("Post Content") self.text_edit.setAccessibleDescription("Enter your post content here. Type @ for mentions, : for emojis. Press Tab to move to post options.") self.text_edit.setPlaceholderText("What's on your mind? Type @ for mentions, : for emojis")