Skip to content

fix: eliminate ticking sound and filter erroneous STT responses (#3) - #4

Open
TrivCodez wants to merge 4 commits into
finityfly:mainfrom
TrivCodez:fix/ticking-sound-and-erroneous-responses
Open

fix: eliminate ticking sound and filter erroneous STT responses (#3)#4
TrivCodez wants to merge 4 commits into
finityfly:mainfrom
TrivCodez:fix/ticking-sound-and-erroneous-responses

Conversation

@TrivCodez

@TrivCodez TrivCodez commented Jul 9, 2026

Copy link
Copy Markdown

Description

Fixes issue #3 — two related bugs in the STS pipeline that made it produce audible "ticking" sounds and respond with non-speech content.

Root Cause

The STT engine (stt_engine.py) used a fixed silence threshold of 800 that was far too sensitive. Ambient room noise — tapping, breathing, fan hum, clicks — routinely exceeded this threshold. When even ~0.19s (3 chunks) of noise reached Groq Whisper, the model frequently hallucinated short words or syllables. These hallucinated fragments were then translated by the MT processor and synthesised by TTS, producing:

  • Short audible bursts — the "ticking" sound
  • Unintended responses — the system speaking non-speech content back to the user

Additionally, the old hallucination filter only blocked 4 hard-coded phrases, leaving dozens of common Whisper noise hallucinations uncaught.

Changes Made (src/utils/stt_engine.py)

Change Before After Why
Noise floor calibration Hard-coded SILENCE_THRESHOLD = 800 Adaptive calibration: samples 1s of ambient audio on startup, sets threshold to max(mean_rms × 3.5, 800) Adapts to the user's actual environment — quiet rooms and noisy spaces both work correctly
Minimum utterance length MIN_SPEECH_CHUNKS = 3 (~0.19s) MIN_SPEECH_CHUNKS = 8 (~0.5s) Eliminates clicks, pops, and short noise bursts from reaching Whisper
Silence finalisation MAX_SILENT_CHUNKS = 12 (~0.77s) MAX_SILENT_CHUNKS = 15 (~0.96s) Gives slightly more padding so natural pauses in speech aren't cut off prematurely
Hallucination filter 4 hard-coded phrases ("thank you", "thanks for watching", "subtitle", "bye bye") Comprehensive _is_hallucination() method: ~30 known hallucination phrases + structural checks (min length, alphabetic content, repeated chars) Catches noise hallucinations that would otherwise become "ticking" sounds
Logging Silent discarding of short utterances [STT] Filtered hallucination: '...' logged for every discarded transcription Makes the filter behaviour observable for future debugging

Testing Notes

  • The noise calibration runs once at startup (first ~1s) — the user should remain quiet during this window.
  • Calibration adapts to the environment, so both quiet rooms and noisier spaces are handled.
  • The threshold is recalculated on each start() call, so re-running the script recalibrates.

Closes #3

Summary by CodeRabbit

  • Bug Fixes
    • Improved speech recognition reliability by adding adaptive ambient-noise calibration and dynamically tuning silence/speech detection thresholds.
    • Reduced false transcriptions with stronger “no speech”/hallucination filtering and centralized text validation before enqueueing.
    • Made recording start/stop behavior more consistent by refining the audio buffering state logic (using clearer speech vs. trailing-silence tracking to finalize utterances reliably).

Root cause — The STT engine's SILENCE_THRESHOLD (800) was far too sensitive.
Ambient room noise and incidental sounds (tapping, breathing, fan hum) routinely
exceeded this threshold. When even ~0.19s of noise reached Whisper, it frequently
hallucinated short words or syllables. These were then translated by MT and
synthesised by TTS, producing audible "tick" bursts and non-speech responses.

Changes (stt_engine.py):
- Calibrate an adaptive noise floor on startup: sample 1s of ambient audio,
  compute mean RMS, and set SILENCE_THRESHOLD to max(mean × 3.5, 800).
  This adapts to the user's actual environment rather than using a fixed value.
- Raise MIN_SPEECH_CHUNKS from 3 to 8, requiring ~0.5s of continuous audio
  above threshold before sending to Whisper. This eliminates clicks, pops, and
  short bursts of noise from being transcribed.
- Replace the simplistic 4-phrase hallucinations filter with a comprehensive
  filter that catches: common Whisper noise hallucinations, single-character
  utterances, phrases under 3 characters, and known filler/hallucination patterns.
- Add a minimum-confidence gate: transcribed text must contain a real word
  (alphabetic content) to be accepted — pure numbers, punctuation, or symbols
  from noise are discarded.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@TrivCodez, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02be80a9-0881-4faa-ab98-502c84ff51bb

📥 Commits

Reviewing files that changed from the base of the PR and between dc3407f and 4720b01.

📒 Files selected for processing (1)
  • src/utils/stt_engine.py
📝 Walkthrough

Walkthrough

The STT engine now calibrates ambient noise, uses RMS-based speech gating, requires minimum speech content before enqueueing audio, filters hallucinated transcripts centrally, and changes the thread started by start().

Changes

STT Engine Noise Calibration and Filtering

Layer / File(s) Summary
Imports and gating configuration
src/utils/stt_engine.py
Adds the time import and updates silence, speech, and hallucination-filter configuration.
Noise calibration and hallucination detection helpers
src/utils/stt_engine.py
Adds RMS calculation, ambient noise-floor calibration, and centralized hallucination detection.
Audio capture loop gating rewrite
src/utils/stt_engine.py
Rewrites streaming capture to use calibrated RMS thresholds, trailing-silence counting, and minimum speech-chunk requirements.
Transcription filtering and thread startup
src/utils/stt_engine.py
Filters hallucination-like transcripts before queueing and changes start() to launch self._thread for the stream loop.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: reducing ticking noise and filtering bad STT output.
Linked Issues check ✅ Passed The changes address issue #3 by calibrating silence, extending speech/silence gating, and filtering hallucinated STT output.
Out of Scope Changes check ✅ Passed All changes stay within STT noise reduction and hallucination filtering needed for issue #3.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/utils/stt_engine.py`:
- Line 67: The RMS threshold comment in stt_engine is using the non-ASCII
multiplication symbol, which Ruff flags. Update the wording in the comment near
the threshold calculation to use plain ASCII "x" instead of "×", keeping the
meaning the same and preserving the reference to the mean_rms threshold
description.
- Around line 119-122: The hallucination check in the STT engine is too
aggressive because `self.HALLUCINATIONS` is matched with `startswith` and
`endswith`, causing common short words to flag valid speech. Update the matching
logic in the relevant loop inside the STT engine method so it only treats a
phrase as a hallucination when it is an exact standalone match or otherwise
clearly bounded, and avoid prefix/suffix matching for entries like “a”, “to”,
“in”, and “is”. Keep the existing `lower` normalization and
`self.HALLUCINATIONS` iteration, but tighten the condition to prevent valid
sentences from being rejected.
- Around line 155-179: The utterance-length check in the speech capture loop is
using len(frames), which counts trailing silence and can let noise bursts pass
as real speech. Update the buffering logic in the chunk-processing loop to track
only speech chunks (for example, a separate counter in the recording_started
path of the STT engine) and use that count for the MIN_SPEECH_CHUNKS gate before
audio_queue.put. Keep the existing silence reset behavior, but ensure only
non-silent speech frames contribute to the threshold.
- Around line 83-102: The calibration stream in the STT noise calibration flow
is not guaranteed to be closed if an exception occurs during `read()` or
`_calculate_rms()`, which can leave the input device busy. Update the
calibration logic in the noise calibration method that opens `cal_stream` so
`stop_stream()` and `close()` are always executed even on failure, using a
cleanup path such as a guarded/finally-style close around the loop. Keep the
existing threshold calculation and logging behavior intact, but ensure
`cal_stream` is released before the exception handler logs the fallback message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b2be30d-3886-49a2-88a9-914df0553b1d

📥 Commits

Reviewing files that changed from the base of the PR and between 864cb82 and a4bb800.

📒 Files selected for processing (1)
  • src/utils/stt_engine.py

Comment thread src/utils/stt_engine.py Outdated
Comment thread src/utils/stt_engine.py
Comment thread src/utils/stt_engine.py Outdated
Comment thread src/utils/stt_engine.py
1. Replace ambiguous non-ASCII × with plain x in docstring (Ruff RUF002)
2. Guard calibration stream with try/finally to ensure device is released
   on failure paths — prevents the input device staying busy
3. Only match hallucination phrases as exact matches (not startswith/endswith)
   to avoid false positives on valid speech like "a quick test" or "to be"
4. Track speech_chunks_count separately so trailing silence frames don't
   inflate the counter — a single noise spike followed by silence can no
   longer satisfy MIN_SPEECH_CHUNKS
@TrivCodez

Copy link
Copy Markdown
Author

Thanks for the review, CodeRabbit — all 4 suggestions have been addressed in the latest commit (c8cbff3):

  1. Ambiguous × → x in docstring — Replaced the non-ASCII multiplication sign with plain ASCII x (RUF002)
  2. Calibration stream not closed on failure — Moved cal_stream outside the try block and added a finally guard that closes it regardless of exceptions
  3. startswith/endswith false positives — Changed to exact match only (phrase == lower) so valid speech like "a quick test" or "to be or not to be" won't be filtered
  4. silence frames inflating MIN_SPEECH_CHUNKS — Added speech_chunks_count that only increments on above-threshold chunks; the gate now checks speech_chunks_count >= self.MIN_SPEECH_CHUNKS instead of len(frames), so trailing silence no longer counts toward the minimum utterance length

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/utils/stt_engine.py`:
- Around line 124-128: Strip surrounding punctuation before the exact-match
hallucination check in the stt_engine logic. In the loop over
self.HALLUCINATIONS, normalize the lower text so phrases like “thank you.” and
“bye bye!” match the same exact values as “thank you” and “bye bye” without
broadening the filter. Keep the existing exact-match behavior in this matching
block, just update the normalization used before comparing phrase == lower.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d60b0d16-ec98-4e2b-b67b-dc475f15df49

📥 Commits

Reviewing files that changed from the base of the PR and between a4bb800 and c8cbff3.

📒 Files selected for processing (1)
  • src/utils/stt_engine.py

Comment thread src/utils/stt_engine.py
Whisper often appends punctuation like "." or "!" to transcriptions,
so "thank you." and "bye bye!" miss the exact-match check against
HALLUCINATIONS entries and leak through as valid speech.

Strip common trailing punctuation before comparison to catch these
variants without broadening the filter to startswith/endswith.

Addresses finityfly#4 (comment)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/utils/stt_engine.py`:
- Around line 265-269: Fix start() so it starts the initialized
self._stream_thread instead of the undefined self._thread, then start
self._process_thread as currently intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40ad4c29-7c65-43aa-9e21-aea1216d10cb

📥 Commits

Reviewing files that changed from the base of the PR and between c8cbff3 and dc3407f.

📒 Files selected for processing (1)
  • src/utils/stt_engine.py

Comment thread src/utils/stt_engine.py
@TrivCodez

Copy link
Copy Markdown
Author

Thanks for the thorough review — all feedback has been addressed.

Changes made

Commit c8cbff3 (4 fixes from initial review round):

  • Replaced ambiguous × with plain x in docstring to clear Ruff RUF002
  • Wrapped calibration stream in try/finally so the input device is always released, even on exception
  • Removed startswith/endswith matching from hallucination filter — only exact matches now, preventing false positives on valid speech
  • Introduced speech_chunks_count to gate MIN_SPEECH_CHUNKS instead of len(frames), so trailing silence doesn't inflate the counter

Commit 4720b01 (2 fixes from the second review round):

  • Added lower.rstrip(string.punctuation) before exact-match comparison — catches Whisper outputs like "thank you." and "bye bye!" without widening the filter
  • Fixed typo in start()self._thread.start()self._stream_thread.start()

The PR should be ready to merge now. Let me know if there's anything else you'd like adjusted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Annoying ticking sound in sts_main.py

1 participant