Change pitch and volume added to voice settings.
This commit is contained in:
@@ -5,6 +5,8 @@ Dates are given for the image. All items listed are available for the listed ima
|
|||||||
|
|
||||||
## September 1, 2025
|
## September 1, 2025
|
||||||
|
|
||||||
|
- Hopefully fix a bug where setting default voice would sometimes not save across reboots.
|
||||||
|
- Add pitch and volume parameters to the rate script
|
||||||
- Initial battery monitoring setup
|
- Initial battery monitoring setup
|
||||||
- Make image size for x86_64 slightly smaller (whew, scary!)
|
- Make image size for x86_64 slightly smaller (whew, scary!)
|
||||||
- Remove safety check for install to disk
|
- Remove safety check for install to disk
|
||||||
|
|||||||
@@ -1019,7 +1019,7 @@ if __name__ == "__main__":
|
|||||||
menu.add_item("System", "Internet Configuration", "GAME=\"Network Configuration\" /home/stormux/.clirc")
|
menu.add_item("System", "Internet Configuration", "GAME=\"Network Configuration\" /home/stormux/.clirc")
|
||||||
menu.add_item("System", "Enable Screen", lambda: menu.toggle_screen("screen"))
|
menu.add_item("System", "Enable Screen", lambda: menu.toggle_screen("screen"))
|
||||||
menu.add_item("System", "Disable Screen", lambda: menu.toggle_screen("headless"))
|
menu.add_item("System", "Disable Screen", lambda: menu.toggle_screen("headless"))
|
||||||
menu.add_item("System", "Set System Default Speech Rate", "/usr/local/bin/speechd_rate.py")
|
menu.add_item("System", "Set System Speech Settings", "/usr/local/bin/speechd_rate.py")
|
||||||
menu.add_item("System", "Set Default Voice", "/usr/local/bin/set-voice.py")
|
menu.add_item("System", "Set Default Voice", "/usr/local/bin/set-voice.py")
|
||||||
menu.add_item("System", "Set Timezone", "GAME='Set Timezone' /home/stormux/.clirc")
|
menu.add_item("System", "Set Timezone", "GAME='Set Timezone' /home/stormux/.clirc")
|
||||||
menu.add_item("System", "Upload Files", "/home/stormux/.local/upload_server/uploader.py")
|
menu.add_item("System", "Upload Files", "/home/stormux/.local/upload_server/uploader.py")
|
||||||
|
|||||||
@@ -147,19 +147,48 @@ class VoiceSelectionMenu:
|
|||||||
# Clean up before executing system commands
|
# Clean up before executing system commands
|
||||||
self.cleanup(full_cleanup=False)
|
self.cleanup(full_cleanup=False)
|
||||||
|
|
||||||
# Use sed to update the DefaultModule in speechd.conf
|
# Read the current config file
|
||||||
sed_cmd = f"sudo sed -i '/^\\s*#\\?\\s*DefaultModule\\s\\+/c\\DefaultModule {module}' /etc/speech-dispatcher/speechd.conf"
|
import re
|
||||||
subprocess.run(sed_cmd, shell=True, check=True)
|
with open('/etc/speech-dispatcher/speechd.conf', 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
# Restart speech-dispatcher
|
# Check if DefaultModule is already uncommented
|
||||||
|
if re.search(r'^\s*DefaultModule\s+', content, re.MULTILINE):
|
||||||
|
# Replace existing DefaultModule line
|
||||||
|
new_content = re.sub(
|
||||||
|
r'^(\s*)DefaultModule\s+\S+',
|
||||||
|
f'\\1DefaultModule {module}',
|
||||||
|
content,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Uncomment and set DefaultModule line
|
||||||
|
new_content = re.sub(
|
||||||
|
r'^(\s*)#\s*DefaultModule\s+\S*',
|
||||||
|
f'\\1DefaultModule {module}',
|
||||||
|
content,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write to a temporary file
|
||||||
|
temp_file = "/tmp/speechd.conf.new"
|
||||||
|
with open(temp_file, 'w') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
# Use sudo to move the file to the correct location
|
||||||
|
subprocess.run(f"sudo mv {temp_file} /etc/speech-dispatcher/speechd.conf", shell=True, check=True)
|
||||||
|
|
||||||
|
# Restart speech-dispatcher more thoroughly
|
||||||
|
subprocess.run("sudo systemctl restart speech-dispatcher", shell=True, check=False)
|
||||||
|
# Also kill any remaining processes
|
||||||
subprocess.run("sudo killall speech-dispatcher", shell=True, check=False)
|
subprocess.run("sudo killall speech-dispatcher", shell=True, check=False)
|
||||||
|
|
||||||
# Re-initialize speech after changes
|
# Re-initialize speech after changes
|
||||||
time.sleep(1) # Give a moment for the service to restart
|
time.sleep(2) # Give more time for the service to restart
|
||||||
self.init_speech()
|
self.init_speech()
|
||||||
|
|
||||||
# Notify the user that the change is complete - should not be interrupted
|
# Notify the user that the change is complete - should not be interrupted
|
||||||
self.speak(f"The {module} module is now being used for this system.", interrupt=False)
|
self.speak(f"The {module} module is now set as the default voice for this system.", interrupt=False)
|
||||||
|
|
||||||
# Return to the menu after speech finishes
|
# Return to the menu after speech finishes
|
||||||
# No sleep here - the next UI repaint doesn't depend on speech finishing
|
# No sleep here - the next UI repaint doesn't depend on speech finishing
|
||||||
|
|||||||
+183
-58
@@ -12,55 +12,78 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
class SpeechRateMenu:
|
class SpeechRateMenu:
|
||||||
def __init__(self, title="Speech Rate Configuration"):
|
def __init__(self, title="Speech Configuration"):
|
||||||
self.title = title
|
self.title = title
|
||||||
self.currentRate = 0 # Default rate
|
self.currentRate = 0 # Default rate
|
||||||
|
self.currentVolume = 100 # Default volume
|
||||||
|
self.currentPitch = 0 # Default pitch
|
||||||
|
self.currentMode = 0 # 0=Rate, 1=Volume, 2=Pitch
|
||||||
|
self.modes = ["Rate", "Volume", "Pitch"]
|
||||||
self.stdscr = None
|
self.stdscr = None
|
||||||
self.cursesInitialized = False # Flag to track if curses has been initialized
|
self.cursesInitialized = False # Flag to track if curses has been initialized
|
||||||
self.configFile = "/etc/speech-dispatcher/speechd.conf"
|
self.configFile = "/etc/speech-dispatcher/speechd.conf"
|
||||||
|
|
||||||
# Load current rate from config FIRST
|
# Load current settings from config FIRST
|
||||||
self.load_current_rate()
|
self.load_current_settings()
|
||||||
|
|
||||||
# Initialize speech client AFTER loading the rate
|
# Initialize speech client AFTER loading the settings
|
||||||
self.speechClient = None
|
self.speechClient = None
|
||||||
self.init_speech()
|
self.init_speech()
|
||||||
|
|
||||||
def init_speech(self):
|
def init_speech(self):
|
||||||
"""Initialize the speech client"""
|
"""Initialize the speech client"""
|
||||||
try:
|
try:
|
||||||
self.speechClient = speechd.SSIPClient("speech_rate_menu")
|
self.speechClient = speechd.SSIPClient("speech_config_menu")
|
||||||
self.speechClient.set_priority(speechd.Priority.IMPORTANT)
|
self.speechClient.set_priority(speechd.Priority.IMPORTANT)
|
||||||
self.speechClient.set_punctuation(speechd.PunctuationMode.SOME)
|
self.speechClient.set_punctuation(speechd.PunctuationMode.SOME)
|
||||||
|
|
||||||
# Apply the loaded rate to the speech client
|
# Apply the loaded settings to the speech client
|
||||||
self.speechClient.set_rate(self.currentRate)
|
self.speechClient.set_rate(self.currentRate)
|
||||||
|
self.speechClient.set_volume(self.currentVolume)
|
||||||
|
self.speechClient.set_pitch(self.currentPitch)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Fallback to None - the speak method will handle this
|
# Fallback to None - the speak method will handle this
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def load_current_rate(self):
|
def load_current_settings(self):
|
||||||
"""Load the current default rate from speechd.conf"""
|
"""Load the current default settings from speechd.conf"""
|
||||||
try:
|
try:
|
||||||
with open(self.configFile, 'r') as f:
|
with open(self.configFile, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# First check for uncommented DefaultRate with flexible whitespace
|
# Load Rate
|
||||||
activeMatch = re.search(r'^\s*DefaultRate\s+(-?\d+)', content, re.MULTILINE)
|
activeMatch = re.search(r'^\s*DefaultRate\s+(-?\d+)', content, re.MULTILINE)
|
||||||
if activeMatch:
|
if activeMatch:
|
||||||
self.currentRate = int(activeMatch.group(1))
|
self.currentRate = int(activeMatch.group(1))
|
||||||
else:
|
else:
|
||||||
# If DefaultRate is commented out, get the value from commented line
|
|
||||||
commentedMatch = re.search(r'^\s*#\s*DefaultRate\s+(-?\d+)', content, re.MULTILINE)
|
commentedMatch = re.search(r'^\s*#\s*DefaultRate\s+(-?\d+)', content, re.MULTILINE)
|
||||||
if commentedMatch:
|
if commentedMatch:
|
||||||
self.currentRate = int(commentedMatch.group(1))
|
self.currentRate = int(commentedMatch.group(1))
|
||||||
|
|
||||||
|
# Load Volume
|
||||||
|
activeMatch = re.search(r'^\s*DefaultVolume\s+(-?\d+)', content, re.MULTILINE)
|
||||||
|
if activeMatch:
|
||||||
|
self.currentVolume = int(activeMatch.group(1))
|
||||||
|
else:
|
||||||
|
commentedMatch = re.search(r'^\s*#\s*DefaultVolume\s+(-?\d+)', content, re.MULTILINE)
|
||||||
|
if commentedMatch:
|
||||||
|
self.currentVolume = int(commentedMatch.group(1))
|
||||||
|
|
||||||
|
# Load Pitch
|
||||||
|
activeMatch = re.search(r'^\s*DefaultPitch\s+(-?\d+)', content, re.MULTILINE)
|
||||||
|
if activeMatch:
|
||||||
|
self.currentPitch = int(activeMatch.group(1))
|
||||||
|
else:
|
||||||
|
commentedMatch = re.search(r'^\s*#\s*DefaultPitch\s+(-?\d+)', content, re.MULTILINE)
|
||||||
|
if commentedMatch:
|
||||||
|
self.currentPitch = int(commentedMatch.group(1))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# If loading fails, we'll use default value 0
|
# If loading fails, we'll use default values
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def save_rate_to_config(self):
|
def save_settings_to_config(self):
|
||||||
"""Save the current rate to the speech-dispatcher config file"""
|
"""Save the current settings to the speech-dispatcher config file"""
|
||||||
try:
|
try:
|
||||||
# We need to use sudo to modify the system config file
|
# We need to use sudo to modify the system config file
|
||||||
# This assumes the user has sudo privileges or the script is run as root
|
# This assumes the user has sudo privileges or the script is run as root
|
||||||
@@ -69,21 +92,53 @@ class SpeechRateMenu:
|
|||||||
with open(self.configFile, 'r') as f:
|
with open(self.configFile, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# Check if DefaultRate is already uncommented
|
newContent = content
|
||||||
if re.search(r'^\s*DefaultRate\s+', content, re.MULTILINE):
|
|
||||||
# Replace the existing DefaultRate line, preserving leading whitespace
|
# Handle DefaultRate
|
||||||
|
if re.search(r'^\s*DefaultRate\s+', newContent, re.MULTILINE):
|
||||||
newContent = re.sub(
|
newContent = re.sub(
|
||||||
r'^(\s*)DefaultRate\s+(-?\d+)',
|
r'^(\s*)DefaultRate\s+(-?\d+)',
|
||||||
r'\1DefaultRate ' + str(self.currentRate),
|
r'\1DefaultRate ' + str(self.currentRate),
|
||||||
content,
|
newContent,
|
||||||
flags=re.MULTILINE
|
flags=re.MULTILINE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Uncomment and update the DefaultRate line, preserving leading whitespace
|
|
||||||
newContent = re.sub(
|
newContent = re.sub(
|
||||||
r'^(\s*)#\s*DefaultRate\s+(-?\d+)',
|
r'^(\s*)#\s*DefaultRate\s+(-?\d+)',
|
||||||
r'\1DefaultRate ' + str(self.currentRate),
|
r'\1DefaultRate ' + str(self.currentRate),
|
||||||
content,
|
newContent,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle DefaultVolume
|
||||||
|
if re.search(r'^\s*DefaultVolume\s+', newContent, re.MULTILINE):
|
||||||
|
newContent = re.sub(
|
||||||
|
r'^(\s*)DefaultVolume\s+(-?\d+)',
|
||||||
|
r'\1DefaultVolume ' + str(self.currentVolume),
|
||||||
|
newContent,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
newContent = re.sub(
|
||||||
|
r'^(\s*)#\s*DefaultVolume\s+(-?\d+)',
|
||||||
|
r'\1DefaultVolume ' + str(self.currentVolume),
|
||||||
|
newContent,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle DefaultPitch
|
||||||
|
if re.search(r'^\s*DefaultPitch\s+', newContent, re.MULTILINE):
|
||||||
|
newContent = re.sub(
|
||||||
|
r'^(\s*)DefaultPitch\s+(-?\d+)',
|
||||||
|
r'\1DefaultPitch ' + str(self.currentPitch),
|
||||||
|
newContent,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
newContent = re.sub(
|
||||||
|
r'^(\s*)#\s*DefaultPitch\s+(-?\d+)',
|
||||||
|
r'\1DefaultPitch ' + str(self.currentPitch),
|
||||||
|
newContent,
|
||||||
flags=re.MULTILINE
|
flags=re.MULTILINE
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,19 +155,50 @@ class SpeechRateMenu:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def adjust_rate(self, amount):
|
def get_current_value(self):
|
||||||
"""Adjust the speech rate by the given amount"""
|
"""Get the current value for the active mode"""
|
||||||
# Rate should be between -50 and 100
|
if self.currentMode == 0: # Rate
|
||||||
newRate = max(-50, min(100, self.currentRate + amount))
|
return self.currentRate
|
||||||
|
elif self.currentMode == 1: # Volume
|
||||||
if newRate != self.currentRate:
|
return self.currentVolume
|
||||||
self.currentRate = newRate
|
else: # Pitch
|
||||||
if self.speechClient:
|
return self.currentPitch
|
||||||
try:
|
|
||||||
self.speechClient.set_rate(self.currentRate)
|
def adjust_current_value(self, amount):
|
||||||
self.speak(f"Speech rate {self.currentRate}")
|
"""Adjust the current value by the given amount"""
|
||||||
except Exception:
|
if self.currentMode == 0: # Rate
|
||||||
pass
|
# Rate should be between -50 and 100
|
||||||
|
newValue = max(-50, min(100, self.currentRate + amount))
|
||||||
|
if newValue != self.currentRate:
|
||||||
|
self.currentRate = newValue
|
||||||
|
if self.speechClient:
|
||||||
|
try:
|
||||||
|
self.speechClient.set_rate(self.currentRate)
|
||||||
|
self.speak(f"Speech rate {self.currentRate}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
elif self.currentMode == 1: # Volume
|
||||||
|
# Volume should be between -100 and 100
|
||||||
|
newValue = max(-100, min(100, self.currentVolume + amount))
|
||||||
|
if newValue != self.currentVolume:
|
||||||
|
self.currentVolume = newValue
|
||||||
|
if self.speechClient:
|
||||||
|
try:
|
||||||
|
self.speechClient.set_volume(self.currentVolume)
|
||||||
|
self.speak(f"Volume {self.currentVolume}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else: # Pitch
|
||||||
|
# Pitch should be between -100 and 100
|
||||||
|
newValue = max(-100, min(100, self.currentPitch + amount))
|
||||||
|
if newValue != self.currentPitch:
|
||||||
|
self.currentPitch = newValue
|
||||||
|
if self.speechClient:
|
||||||
|
try:
|
||||||
|
self.speechClient.set_pitch(self.currentPitch)
|
||||||
|
self.speak(f"Pitch {self.currentPitch}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def speak(self, text, interrupt=True):
|
def speak(self, text, interrupt=True):
|
||||||
"""Speak the given text with option to interrupt existing speech"""
|
"""Speak the given text with option to interrupt existing speech"""
|
||||||
@@ -156,26 +242,55 @@ class SpeechRateMenu:
|
|||||||
self.stdscr.addstr(1, x, title, curses.A_BOLD)
|
self.stdscr.addstr(1, x, title, curses.A_BOLD)
|
||||||
|
|
||||||
# Draw help line
|
# Draw help line
|
||||||
helpText = "Up/Down: Adjust Rate | Enter: Save | Q/Esc: Quit"
|
helpText = "Up/Down: Adjust | Tab: Switch Mode | Enter: Save | Q/Esc: Quit"
|
||||||
x = max(0, w // 2 - len(helpText) // 2)
|
x = max(0, w // 2 - len(helpText) // 2)
|
||||||
self.stdscr.addstr(3, x, helpText)
|
self.stdscr.addstr(3, x, helpText)
|
||||||
|
|
||||||
# Draw current rate
|
# Draw all current values
|
||||||
rateText = f"Current Rate: {self.currentRate}"
|
currentMode = self.modes[self.currentMode]
|
||||||
x = max(0, w // 2 - len(rateText) // 2)
|
|
||||||
self.stdscr.addstr(5, x, rateText, curses.A_REVERSE)
|
# Rate display
|
||||||
|
rateText = f"Rate: {self.currentRate}"
|
||||||
|
attr = curses.A_REVERSE if self.currentMode == 0 else curses.A_NORMAL
|
||||||
|
x = max(0, w // 2 - 30)
|
||||||
|
self.stdscr.addstr(5, x, rateText, attr)
|
||||||
|
|
||||||
|
# Volume display
|
||||||
|
volumeText = f"Volume: {self.currentVolume}"
|
||||||
|
attr = curses.A_REVERSE if self.currentMode == 1 else curses.A_NORMAL
|
||||||
|
x = max(0, w // 2 - 5)
|
||||||
|
self.stdscr.addstr(5, x, volumeText, attr)
|
||||||
|
|
||||||
|
# Pitch display
|
||||||
|
pitchText = f"Pitch: {self.currentPitch}"
|
||||||
|
attr = curses.A_REVERSE if self.currentMode == 2 else curses.A_NORMAL
|
||||||
|
x = max(0, w // 2 + 20)
|
||||||
|
self.stdscr.addstr(5, x, pitchText, attr)
|
||||||
|
|
||||||
# Draw rate visualization
|
# Current mode indicator
|
||||||
|
modeText = f"Current Mode: {currentMode}"
|
||||||
|
x = max(0, w // 2 - len(modeText) // 2)
|
||||||
|
self.stdscr.addstr(7, x, modeText, curses.A_BOLD)
|
||||||
|
|
||||||
|
# Draw visualization bar for current parameter
|
||||||
barWidth = 50 # Width of the visualization bar
|
barWidth = 50 # Width of the visualization bar
|
||||||
barX = max(0, w // 2 - barWidth // 2)
|
barX = max(0, w // 2 - barWidth // 2)
|
||||||
|
|
||||||
# Map rate (-50 to 100) to bar position (0 to barWidth)
|
# Get current value and range
|
||||||
rateRange = 150 # Total range (from -50 to 100)
|
currentValue = self.get_current_value()
|
||||||
normalizedRate = self.currentRate + 50 # Shift to 0-150 range
|
if self.currentMode == 0: # Rate
|
||||||
position = int((normalizedRate / rateRange) * barWidth)
|
minVal, maxVal = -50, 100
|
||||||
|
totalRange = 150
|
||||||
|
normalizedValue = currentValue + 50
|
||||||
|
else: # Volume or Pitch
|
||||||
|
minVal, maxVal = -100, 100
|
||||||
|
totalRange = 200
|
||||||
|
normalizedValue = currentValue + 100
|
||||||
|
|
||||||
|
position = int((normalizedValue / totalRange) * barWidth)
|
||||||
|
|
||||||
# Draw the bar
|
# Draw the bar
|
||||||
barY = 7
|
barY = 9
|
||||||
self.stdscr.addstr(barY, barX, "┌" + "─" * barWidth + "┐")
|
self.stdscr.addstr(barY, barX, "┌" + "─" * barWidth + "┐")
|
||||||
self.stdscr.addstr(barY + 1, barX, "│" + " " * barWidth + "│")
|
self.stdscr.addstr(barY + 1, barX, "│" + " " * barWidth + "│")
|
||||||
self.stdscr.addstr(barY + 2, barX, "└" + "─" * barWidth + "┘")
|
self.stdscr.addstr(barY + 2, barX, "└" + "─" * barWidth + "┘")
|
||||||
@@ -185,11 +300,12 @@ class SpeechRateMenu:
|
|||||||
self.stdscr.addstr(barY + 1, barX + 1 + position, "█", curses.A_BOLD)
|
self.stdscr.addstr(barY + 1, barX + 1 + position, "█", curses.A_BOLD)
|
||||||
|
|
||||||
# Add labels for min and max
|
# Add labels for min and max
|
||||||
self.stdscr.addstr(barY + 3, barX, "-50")
|
self.stdscr.addstr(barY + 3, barX, str(minVal))
|
||||||
self.stdscr.addstr(barY + 3, barX + barWidth - 3, "100")
|
maxLabel = str(maxVal)
|
||||||
|
self.stdscr.addstr(barY + 3, barX + barWidth - len(maxLabel), maxLabel)
|
||||||
|
|
||||||
# Note about saving
|
# Note about saving
|
||||||
note = "Press Enter to save the rate to system config"
|
note = "Press Enter to save all settings to system config"
|
||||||
x = max(0, w // 2 - len(note) // 2)
|
x = max(0, w // 2 - len(note) // 2)
|
||||||
self.stdscr.addstr(h - 3, x, note, curses.A_DIM)
|
self.stdscr.addstr(h - 3, x, note, curses.A_DIM)
|
||||||
|
|
||||||
@@ -245,7 +361,8 @@ class SpeechRateMenu:
|
|||||||
self.draw_menu()
|
self.draw_menu()
|
||||||
|
|
||||||
# Welcome message
|
# Welcome message
|
||||||
self.speak(f"The current rate for the default voice is {self.currentRate}.")
|
currentMode = self.modes[self.currentMode]
|
||||||
|
self.speak(f"Speech configuration menu. Currently adjusting {currentMode}. Rate {self.currentRate}, Volume {self.currentVolume}, Pitch {self.currentPitch}.")
|
||||||
|
|
||||||
# Main loop
|
# Main loop
|
||||||
while True:
|
while True:
|
||||||
@@ -256,30 +373,38 @@ class SpeechRateMenu:
|
|||||||
|
|
||||||
# Handle navigation
|
# Handle navigation
|
||||||
if key == curses.KEY_UP:
|
if key == curses.KEY_UP:
|
||||||
# Increase rate by 10
|
# Increase current value by 10
|
||||||
self.adjust_rate(10)
|
self.adjust_current_value(10)
|
||||||
self.draw_menu()
|
self.draw_menu()
|
||||||
|
|
||||||
elif key == curses.KEY_DOWN:
|
elif key == curses.KEY_DOWN:
|
||||||
# Decrease rate by 10
|
# Decrease current value by 10
|
||||||
self.adjust_rate(-10)
|
self.adjust_current_value(-10)
|
||||||
|
self.draw_menu()
|
||||||
|
|
||||||
|
elif key == ord('\t') or key == 9: # Tab key
|
||||||
|
# Switch to next mode
|
||||||
|
self.currentMode = (self.currentMode + 1) % len(self.modes)
|
||||||
|
currentMode = self.modes[self.currentMode]
|
||||||
|
currentValue = self.get_current_value()
|
||||||
|
self.speak(f"Switching to {currentMode}. Current value: {currentValue}")
|
||||||
self.draw_menu()
|
self.draw_menu()
|
||||||
|
|
||||||
elif key == curses.KEY_ENTER or key == 10 or key == 13: # Enter key
|
elif key == curses.KEY_ENTER or key == 10 or key == 13: # Enter key
|
||||||
# Save the rate
|
# Save all settings
|
||||||
self.speak("Saving speech rate to system configuration.")
|
self.speak("Saving speech settings to system configuration.")
|
||||||
success = self.save_rate_to_config()
|
success = self.save_settings_to_config()
|
||||||
if success:
|
if success:
|
||||||
self.speak(f"Speech rate {self.currentRate} has been saved successfully.")
|
self.speak(f"Speech settings saved successfully. Rate {self.currentRate}, Volume {self.currentVolume}, Pitch {self.currentPitch}.")
|
||||||
else:
|
else:
|
||||||
self.speak("Failed to save speech rate. You may need root privileges.")
|
self.speak("Failed to save speech settings. You may need root privileges.")
|
||||||
|
|
||||||
# Wait briefly to allow speech to complete before exiting
|
# Wait briefly to allow speech to complete before exiting
|
||||||
time.sleep(3)
|
time.sleep(4)
|
||||||
break # Exit the loop after saving
|
break # Exit the loop after saving
|
||||||
|
|
||||||
elif key == 27 or key == ord('q') or key == ord('Q'): # Esc or Q
|
elif key == 27 or key == ord('q') or key == ord('Q'): # Esc or Q
|
||||||
self.speak("Exiting speech rate configuration.")
|
self.speak("Exiting speech configuration.")
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in New Issue
Block a user