Skip to content

Optimized command.py#11

Open
zhomander wants to merge 6 commits intovannu07:mainfrom
zhomander:patch-1
Open

Optimized command.py#11
zhomander wants to merge 6 commits intovannu07:mainfrom
zhomander:patch-1

Conversation

@zhomander
Copy link

changes

  • Replaced initialization with an instance using double-checked locking
  • Configured voice, rate, and volume properties once at initialization
  • Performed ambient noise calibration once and cached the energy threshold
  • Disabled dynamic energy threshold adjustment to reduce CPU usage
  • Added thread locks to protect access to shared TTS and recognition resources
  • Reduced branching complexity and runtime overhead

@vannu07
Copy link
Owner

vannu07 commented Oct 12, 2025

Hi @alexzhom
Thanks for the contribution — this looks like a great step toward optimizing Jarvis’ command-handling performance!

Summary of your changes:

  • ✅ Implemented double-checked locking for safe initialization.
  • ✅ Configured voice, rate, and volume once at startup to avoid redundant calls.
  • ✅ Added ambient noise calibration caching, reducing unnecessary recalibration.
  • ✅ Disabled dynamic energy threshold to minimize CPU load.
  • ✅ Introduced thread locks for shared resource safety.
  • ✅ Reduced branching complexity and improved runtime efficiency.

Next Steps / Review Points:

  1. Please confirm that the locks do not block speech recognition during active TTS playback.
  2. Add a short inline comment describing where calibration is cached.
  3. Could you include a quick benchmark or observation (e.g., CPU or latency reduction %) after optimization?

Once confirmed, this PR looks ready to merge for Hacktoberfest
Great work on making command.py leaner and more efficient!

@vannu07 vannu07 requested a review from Copilot October 21, 2025 15:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors backend/command.py to optimize performance by implementing singleton patterns with double-checked locking for TTS engine, speech recognizer, and microphone instances. The changes reduce repeated initialization overhead and improve thread safety for concurrent access.

Key Changes:

  • Implemented lazy-initialized singletons with thread locks for TTS engine, speech recognizer, and microphone
  • Cached ambient noise calibration and disabled dynamic energy threshold adjustment
  • Refactored command routing logic into a dedicated _handle_comm() function to reduce branching complexity

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

def _get_microphone() -> sr.Microphone:
global _microphone
if _microphone is None:
_microphone = sr.Microphone()
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The microphone singleton initialization is not protected by a lock, which could lead to a race condition if multiple threads call _get_microphone() simultaneously. Add a lock similar to _engine_lock and _rec_lock to ensure thread-safe initialization.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +64 to +70
def _ensure_calibrated(r: sr.Recognizer, source: sr.AudioSource) -> None:
"""Calibrate ambient noise once and cache the energy threshold."""
global _calibrated, _cached_energy_threshold
if not _calibrated:
r.adjust_for_ambient_noise(source, duration=0.4)
_cached_energy_threshold = r.energy_threshold
_calibrated = True
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The calibration logic has a race condition where multiple threads could enter the if not _calibrated: block simultaneously before _calibrated is set to True. This should use a lock or implement double-checked locking similar to the singleton patterns used elsewhere.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 156 to 158
speak("No command was given.")
eel.ShowHood()
return
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The logic duplicates the speak('No command was given.') call that appears on line 170. Consider removing this redundant error handling since the same condition is handled in the else block below.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

@vannu07
Copy link
Owner

vannu07 commented Jan 1, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 13 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +107
except Exception as e:
print(f"Listen error: {e}")
return None
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The error handling swallows specific exception information. Using a bare except Exception as e without logging the exception type or traceback makes debugging difficult. Consider using print(f"Listen error: {type(e).__name__}: {e}") or proper logging to preserve more diagnostic information.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +137
speak("What message to send?")
msg = takecommand()
if msg:
whatsApp(Phone, msg, "message", name)
else:
speak("I didn't catch the message.")
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

In the message send flow, if takecommand() returns None (due to an error or timeout), the code correctly handles it by speaking "I didn't catch the message." However, this creates a dead-end in the conversation flow. The user would have to restart the entire process. Consider whether this should return to allow the user to retry, or if the function should loop to give the user another chance to provide the message.

Suggested change
speak("What message to send?")
msg = takecommand()
if msg:
whatsApp(Phone, msg, "message", name)
else:
speak("I didn't catch the message.")
max_retries = 3
for attempt in range(max_retries):
speak("What message to send?")
msg = takecommand()
if msg:
whatsApp(Phone, msg, "message", name)
break
else:
speak("I didn't catch the message.")
if attempt < max_retries - 1:
speak("Please say the message again.")
else:
speak("I couldn't get the message. Let's try again later.")

Copilot uses AI. Check for mistakes.
Comment on lines 166 to 169
if query:
if "open" in query:
from backend.feature import openCommand

openCommand(query)
elif "send message" in query or "call" in query or "video call" in query:
from backend.feature import findContact, whatsApp

flag = ""
Phone, name = findContact(query)
if Phone != 0:
if "send message" in query:
flag = "message"
speak("What message to send?")
query = takecommand() # Ask for the message text
elif "call" in query:
flag = "call"
else:
flag = "video call"
whatsApp(Phone, query, flag, name)
elif "on youtube" in query:
from backend.feature import PlayYoutube

PlayYoutube(query)
else:
from backend.feature import chatBot

chatBot(query)
_handle_comm(query)
else:
speak("No command was given.")
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The condition if query: on line 166 has a logical issue. After the changes:

  1. If message is None and takecommand() returns None or empty string (after strip), we return early at line 155-157
  2. If message is None and query is truthy, we reach line 166 with a valid query
  3. If message is not None, query is set to str(message).strip().lower() on line 163, which could be an empty string

This means when a text message is passed but it's empty/whitespace, we'll reach line 166 with an empty query string. The check correctly handles this, but the else clause speaking "No command was given." creates inconsistency: voice commands get this message AND return early, while empty text messages get this message AND continue to the finally block. Consider making behavior consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +61
def _get_microphone() -> sr.Microphone:
global _microphone
if _microphone is None:
_microphone = sr.Microphone()
return _microphone
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The microphone singleton lacks thread-safety protection. Unlike the engine and recognizer which use locks, the microphone is created without synchronization. If multiple threads call _get_microphone() simultaneously, race conditions could lead to multiple microphone instances being created, defeating the purpose of the singleton pattern and potentially causing resource issues.

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +141
elif "call" in query and "video call" not in query:
whatsApp(Phone, query, "call", name)
else:
whatsApp(Phone, query, "video call", name)
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The logic for determining call type has a subtle issue. The condition "call" in query and "video call" not in query (line 138) could fail for queries like "call video call" or similar edge cases where both substrings appear. While unlikely in practice, the order of checks means "call" would match first if "send message" isn't present. Consider restructuring to check for "video call" first (most specific), then "send message", then plain "call" to avoid ambiguity.

Copilot uses AI. Check for mistakes.
if _recognizer is None:
r = sr.Recognizer()
r.pause_threshold = SPEECH_PAUSE_THRESHOLD
r.dynamic_energy_threshold = False
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

Disabling dynamic energy threshold adjustment (line 52) and caching the calibrated threshold is meant to optimize performance. However, this approach assumes ambient noise levels remain constant throughout the application's lifetime. If the environment becomes noisier or quieter, the fixed threshold may cause the recognizer to either pick up too much background noise or miss valid speech. Consider documenting this trade-off or providing a mechanism to recalibrate when needed (e.g., on user request or periodic intervals).

Copilot uses AI. Check for mistakes.
vannu07 and others added 3 commits January 2, 2026 21:44
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@vannu07 vannu07 requested a review from Copilot January 2, 2026 16:15
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 151 to +152
if not query:
return # Exit if no query is received
print(query)
speak("No command was given.")
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

When a voice command is not recognized (takecommand returns None), the function now speaks "No command was given" which is different from the original behavior that silently returned. This could be annoying to users if recognition failures are frequent (e.g., due to background noise). Consider whether this change in user experience is intentional.

Copilot uses AI. Check for mistakes.
print(f"User said: {query}\n")
speak(query)
normalized = query.strip().lower()
return normalized or None
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The function returns None when query is empty after stripping (line 113: "return normalized or None"). However, the check at line 151 is "if not query", which would be True for both None and empty string. This means the empty string case won't trigger the "No command was given" message, creating inconsistent behavior. The original code always returned query.lower() from takecommand, never None on empty strings.

Suggested change
return normalized or None
return normalized

Copilot uses AI. Check for mistakes.
if msg:
whatsApp(Phone, msg, "message", name)
else:
speak("I didn't catch the message.")
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

When takecommand is called recursively for "send message" (line 130), if the user's message is not recognized (msg is None), the code speaks "I didn't catch the message" but still returns from the handler without completing the WhatsApp message flow. The original code would have passed the None/failed query to whatsApp(). Verify that this change in behavior is intentional and that the whatsApp function can handle this properly.

Suggested change
speak("I didn't catch the message.")
speak("I didn't catch the message.")
# Maintain original behavior: still pass the (possibly None) msg to whatsApp
whatsApp(Phone, msg, "message", name)

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +36
if 0 <= TTS_VOICE_ID < len(voices):
eng.setProperty("voice", voices[TTS_VOICE_ID].id)
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The bounds check for TTS_VOICE_ID is helpful, but silently ignoring an invalid voice ID could lead to unexpected voice selection. When the voice ID is out of range, the code skips setting the voice property, meaning the engine will use its default voice. Consider logging a warning or using a fallback voice ID (like 0) to make this behavior more predictable and debuggable.

Copilot uses AI. Check for mistakes.
def _get_microphone() -> sr.Microphone:
global _microphone
if _microphone is None:
_microphone = sr.Microphone()
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

Trailing whitespace after the microphone initialization. This should be removed for code cleanliness.

Suggested change
_microphone = sr.Microphone()
_microphone = sr.Microphone()

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +80
with _engine_lock:
eng.say(s)
eng.runAndWait()
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

Holding the engine lock during the entire TTS operation (eng.say() and eng.runAndWait()) blocks all other threads from using TTS, even though the engine might support concurrent access for reads. This could create a bottleneck where multiple speak() calls from different threads are serialized. Consider if a finer-grained locking strategy would be appropriate, or document that TTS operations are intentionally serialized.

Suggested change
with _engine_lock:
eng.say(s)
eng.runAndWait()
eng.say(s)
eng.runAndWait()

Copilot uses AI. Check for mistakes.
Comment on lines 162 to 165
if query:
if "open" in query:
from backend.feature import openCommand

openCommand(query)
elif "send message" in query or "call" in query or "video call" in query:
from backend.feature import findContact, whatsApp

flag = ""
Phone, name = findContact(query)
if Phone != 0:
if "send message" in query:
flag = "message"
speak("What message to send?")
query = takecommand() # Ask for the message text
elif "call" in query:
flag = "call"
else:
flag = "video call"
whatsApp(Phone, query, flag, name)
elif "on youtube" in query:
from backend.feature import PlayYoutube

PlayYoutube(query)
else:
from backend.feature import chatBot

chatBot(query)
_handle_comm(query)
else:
speak("No command was given.")
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The condition at line 162 checks "if query" after the try block starts, but query is always truthy at this point. In the voice input path, line 151 returns early if not query, and in the text input path, query is assigned from a message that was provided. This check is redundant and the else block at line 165 is unreachable, making this condition unnecessary.

Copilot uses AI. Check for mistakes.
r.pause_threshold = SPEECH_PAUSE_THRESHOLD
r.dynamic_energy_threshold = False
_recognizer = r
return _recognizer
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

Missing blank line between function definitions. PEP 8 style guide recommends two blank lines between top-level function definitions for better readability.

Suggested change
return _recognizer
return _recognizer

Copilot uses AI. Check for mistakes.
eel.senderText(query)
q = str(message).strip()
print(f"Message received: {q}")
eel.senderText(q)
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The text input path doesn't call speak(query) like the voice input path does at line 111. This creates inconsistent behavior where voice commands are echoed back via TTS but text commands are not. If this inconsistency is intentional, it should be documented; otherwise, consider whether text input should also trigger TTS echo.

Suggested change
eel.senderText(q)
eel.senderText(q)
speak(q)

Copilot uses AI. Check for mistakes.
@@ -1,98 +1,170 @@
import eel
import threading
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

Import of 'time' is not used.

Copilot uses AI. Check for mistakes.
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.

2 participants