From a267f500ddf16c2a08d8a35de4377592e228fdb4 Mon Sep 17 00:00:00 2001 From: juanfradb <94815906+juanfradb@users.noreply.github.com> Date: Sat, 30 May 2026 18:37:53 -0300 Subject: [PATCH] fix: play stop sound immediately on release (#426) --- .../speech_recognition/recognition_manager.py | 7 ++++--- tests/test_recognition_manager_advanced.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vocalinux/speech_recognition/recognition_manager.py b/src/vocalinux/speech_recognition/recognition_manager.py index 0d0a7a9..ece93a9 100644 --- a/src/vocalinux/speech_recognition/recognition_manager.py +++ b/src/vocalinux/speech_recognition/recognition_manager.py @@ -1768,6 +1768,10 @@ class SpeechRecognitionManager: # Stop recording FIRST to prevent capturing the stop sound self.should_record = False + # Give immediate release feedback. The recorder may still finish one + # in-flight chunk, so the stop-sound guard below keeps the cue out. + play_stop_sound() + # Wait for audio thread to finish recording and enqueue any pending audio # This is critical to prevent race condition where recognition thread exits # before the final audio segment is enqueued @@ -1791,9 +1795,6 @@ class SpeechRecognitionManager: self._enqueue_audio_segment(self.audio_buffer) self.audio_buffer = [] - # Now play the stop sound (after recording has stopped) - play_stop_sound() - # Wake up recognition thread so it can drain queued segments and stop self._signal_recognition_stop() diff --git a/tests/test_recognition_manager_advanced.py b/tests/test_recognition_manager_advanced.py index b3b5e30..94752f4 100644 --- a/tests/test_recognition_manager_advanced.py +++ b/tests/test_recognition_manager_advanced.py @@ -226,6 +226,23 @@ class TestStartStopRecognition(unittest.TestCase): mgr.stop_recognition() self.assertFalse(mgr.should_record) + def test_stop_sound_plays_before_audio_thread_join(self): + mgr = _make_manager() + mgr.state = RecognitionState.LISTENING + mgr.should_record = True + mgr.audio_thread = MagicMock() + mgr.audio_thread.is_alive.return_value = True + events = [] + mgr.audio_thread.join.side_effect = lambda timeout=None: events.append("join") + + with patch( + "vocalinux.speech_recognition.recognition_manager.play_stop_sound", + side_effect=lambda: events.append("sound"), + ): + mgr.stop_recognition() + + self.assertEqual(events[:2], ["sound", "join"]) + def test_stop_sound_guard_chunk_calculation(self): mgr = _make_manager()