Fixes to the voice driver. It should actually work completely now.

This commit is contained in:
Storm Dragon
2025-06-15 19:52:18 -04:00
parent 72bd334d65
commit 43871cea3c
9 changed files with 122 additions and 805 deletions

View File

@ -3,6 +3,7 @@
import subprocess
import importlib.util
import os
import time
class DynamicVoiceCommand:
"""Dynamic command class for voice selection"""
@ -23,34 +24,42 @@ class DynamicVoiceCommand:
def run(self):
try:
self.env['runtime']['outputManager'].presentText(f"Testing {self.voice} from {self.module}", interrupt=True)
self.env['runtime']['outputManager'].presentText(f"Testing voice {self.voice} from {self.module}. Please wait.", interrupt=True)
# Test voice first
if self.testVoice():
self.env['runtime']['outputManager'].presentText("Voice test completed. Press Enter again to apply.", interrupt=True)
# Brief pause before testing to avoid speech overlap
time.sleep(0.5)
# Test voice
testResult, errorMsg = self.testVoice()
if testResult:
self.env['runtime']['outputManager'].presentText("Voice test completed successfully. Navigate to Apply Tested Voice to use this voice.", interrupt=False, flush=False)
# Store for confirmation
# Store for confirmation (use same variables as apply_tested_voice.py)
self.env['commandBuffer']['lastTestedModule'] = self.module
self.env['commandBuffer']['lastTestedVoice'] = self.voice
self.env['commandBuffer']['pendingVoiceModule'] = self.module
self.env['commandBuffer']['pendingVoiceVoice'] = self.voice
self.env['commandBuffer']['voiceTestCompleted'] = True
self.env['runtime']['outputManager'].playSound('Accept')
else:
self.env['runtime']['outputManager'].presentText("Voice test failed", interrupt=True)
self.env['runtime']['outputManager'].playSound('Error')
self.env['runtime']['outputManager'].presentText(f"Voice test failed: {errorMsg}", interrupt=False, flush=False)
except Exception as e:
self.env['runtime']['outputManager'].presentText(f"Voice selection error: {str(e)}", interrupt=True)
self.env['runtime']['outputManager'].playSound('Error')
self.env['runtime']['outputManager'].presentText(f"Voice selection error: {str(e)}", interrupt=False, flush=False)
def testVoice(self):
"""Test voice with spd-say"""
try:
cmd = ['spd-say', '-o', self.module, '-y', self.voice, self.testMessage]
result = subprocess.run(cmd, timeout=8)
return result.returncode == 0
except Exception:
return False
cmd = ['spd-say', '-C', '-w', '-o', self.module, '-y', self.voice, self.testMessage]
result = subprocess.run(cmd, timeout=8, capture_output=True, text=True)
if result.returncode == 0:
return True, "Voice test successful"
else:
error_msg = result.stderr.strip() if result.stderr else f"Command failed with return code {result.returncode}"
return False, error_msg
except subprocess.TimeoutExpired:
return False, "Voice test timed out"
except Exception as e:
return False, f"Error running voice test: {str(e)}"
def setCallback(self, callback):
pass
@ -80,30 +89,83 @@ class DynamicApplyVoiceCommand:
self.env['runtime']['outputManager'].presentText(f"Applying {voice} from {module}", interrupt=True)
# Apply to runtime settings
# Debug: Show current settings
settingsManager = self.env['runtime']['settingsManager']
settingsManager.settings['speech']['driver'] = 'speechdDriver'
settingsManager.settings['speech']['module'] = module
settingsManager.settings['speech']['voice'] = voice
currentModule = settingsManager.getSetting('speech', 'module')
currentVoice = settingsManager.getSetting('speech', 'voice')
self.env['runtime']['outputManager'].presentText(f"Current: {currentVoice} from {currentModule}", interrupt=False, flush=False)
# Reinitialize speech driver
if 'speechDriver' in self.env['runtime']:
speechDriver = self.env['runtime']['speechDriver']
speechDriver.shutdown()
speechDriver.initialize(self.env)
# Apply to runtime settings with fallback
settingsManager = self.env['runtime']['settingsManager']
# Store old values for safety
oldDriver = settingsManager.getSetting('speech', 'driver')
oldModule = settingsManager.getSetting('speech', 'module')
oldVoice = settingsManager.getSetting('speech', 'voice')
try:
# Apply new settings to runtime only (use setSetting to update settingArgDict)
settingsManager.setSetting('speech', 'driver', 'speechdDriver')
settingsManager.setSetting('speech', 'module', module)
settingsManager.setSetting('speech', 'voice', voice)
self.env['runtime']['outputManager'].presentText("Voice applied successfully!", interrupt=True)
self.env['runtime']['outputManager'].playSound('Accept')
# Apply settings to speech driver directly
if 'speechDriver' in self.env['runtime']:
speechDriver = self.env['runtime']['speechDriver']
# Get current module to see if we're changing modules
currentModule = settingsManager.getSetting('speech', 'module')
moduleChanging = (currentModule != module)
# Set module and voice on driver instance first
speechDriver.setModule(module)
speechDriver.setVoice(voice)
if moduleChanging:
# Module change requires reinitializing the speech driver
self.env['runtime']['outputManager'].presentText(f"Switching from {currentModule} to {module} module", interrupt=True)
speechDriver.shutdown()
speechDriver.initialize(self.env)
# Re-set after initialization
speechDriver.setModule(module)
speechDriver.setVoice(voice)
self.env['runtime']['outputManager'].presentText("Speech driver reinitialized", interrupt=True)
# Debug: verify what was actually set
self.env['runtime']['outputManager'].presentText(f"Speech driver now has module: {speechDriver.module}, voice: {speechDriver.voice}", interrupt=True)
# Force application by speaking a test message
self.env['runtime']['outputManager'].presentText("Voice applied successfully! You should hear this in the new voice.", interrupt=True)
# Brief pause then more speech to test
time.sleep(1)
self.env['runtime']['outputManager'].presentText("Use save settings to make permanent", interrupt=True)
# Clear pending state
self.env['commandBuffer']['voiceTestCompleted'] = False
# Exit vmenu after successful application
self.env['runtime']['vmenuManager'].setActive(False)
except Exception as e:
# Revert on failure
settingsManager.settings['speech']['driver'] = oldDriver
settingsManager.settings['speech']['module'] = oldModule
settingsManager.settings['speech']['voice'] = oldVoice
# Try to reinitialize with old settings
if 'speechDriver' in self.env['runtime']:
try:
speechDriver = self.env['runtime']['speechDriver']
speechDriver.shutdown()
speechDriver.initialize(self.env)
except:
pass # If this fails, at least we tried
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=False, flush=False)
except Exception as e:
self.env['runtime']['outputManager'].presentText(f"Apply failed: {str(e)}", interrupt=True)
self.env['runtime']['outputManager'].playSound('Error')
self.env['runtime']['outputManager'].presentText(f"Apply voice error: {str(e)}", interrupt=False, flush=False)
def setCallback(self, callback):
pass
@ -129,13 +191,9 @@ def addDynamicVoiceMenus(vmenuManager):
for module in modules[:8]: # Limit to 8 modules to keep menu manageable
moduleMenu = {}
# Get voices for this module (limit to prevent huge menus)
# Get voices for this module
voices = getModuleVoices(module)
if voices:
# Limit voices to keep menu usable
if len(voices) > 50:
voices = voices[:50]
moduleMenu['Note: Showing first 50 voices Action'] = createInfoCommand(f"Module {module} has {len(getModuleVoices(module))} voices, showing first 50", env)
# Add voice commands
for voice in voices:

View File

@ -192,3 +192,12 @@ class outputManager():
self.presentText(' review cursor ', interrupt=interrupt_p)
else:
self.presentText(' text cursor ', interrupt=interrupt_p)
def resetSpeechDriver(self):
"""Reset speech driver to clean state - called by settingsManager"""
if 'speechDriver' in self.env['runtime'] and self.env['runtime']['speechDriver']:
try:
self.env['runtime']['speechDriver'].reset()
self.env['runtime']['debug'].writeDebugOut("Speech driver reset successfully", debug.debugLevel.INFO)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut(f"resetSpeechDriver error: {e}", debug.debugLevel.ERROR)