More tweaks to dectalk driver. Think we're nearly there. Hopefully.

This commit is contained in:
Storm Dragon
2026-07-26 21:24:28 -04:00
parent e93aba109a
commit fac3065c6a
4 changed files with 139 additions and 3 deletions
@@ -14,7 +14,11 @@ class driver(hardware_serial_driver):
use_xon_xoff = True
def _speak_bytes(self, text):
return self._clean_text(text).encode("ascii", errors="replace") + b"\x0b"
text = self._neutralize_inline_commands(self._clean_text(text))
return text.encode("ascii", errors="replace") + b"\x0b"
def _neutralize_inline_commands(self, text):
return text.replace("[:", "[ :")
def _rate_command(self, rate):
value = self._scale_around_default(rate, 75, 180, 650)
@@ -19,6 +19,11 @@ class driver(dectalkDriver.driver):
packet_size = 64
write_timeout = 1000
def __init__(self):
super().__init__()
self._speech_cancel_generation = 0
self._speech_cancel_lock = threading.Lock()
def initialize(self, environment):
self.env = environment
self._is_initialized = False
@@ -129,6 +134,7 @@ class driver(dectalkDriver.driver):
def cancel(self):
if not self._is_initialized:
return
self._advance_speech_cancel_generation()
self.clear_buffer()
self._write_endpoint(
self.immediate_endpoint,
@@ -137,8 +143,56 @@ class driver(dectalkDriver.driver):
write_lock=self.immediate_lock,
)
def _write_bytes(self, data, description="data"):
self._write_endpoint(self.speech_endpoint, data, description)
def speak(self, text, queueable=True, ignore_punctuation=False):
if not self._is_initialized:
return
if not queueable:
self.cancel()
if not isinstance(text, str) or text == "":
return
self._debug(
"Hardware speech queued text: "
f"{len(text)} chars, queue_size={self.text_queue.qsize()}",
debug.DebugLevel.INFO,
on_any_level=True,
)
self.text_queue.put(
(self._get_speech_cancel_generation(), text)
)
def _worker(self):
while not self._stop_worker:
queue_item = self.text_queue.get()
if queue_item is None:
return
cancel_generation, text = queue_item
if cancel_generation != self._get_speech_cancel_generation():
continue
try:
data = self._speak_bytes(text)
self._debug(
"Hardware speech worker prepared speech bytes: "
f"{len(data)} bytes",
debug.DebugLevel.INFO,
on_any_level=True,
)
self._write_bytes(data, "speech", cancel_generation)
except Exception as error:
self._debug(
f"Hardware speech worker failed: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
def _write_bytes(self, data, description="data", cancel_generation=None):
if description == "speech" and cancel_generation is None:
cancel_generation = self._get_speech_cancel_generation()
self._write_endpoint(
self.speech_endpoint,
data,
description,
cancel_generation=cancel_generation,
)
def _write_endpoint(
self,
@@ -146,6 +200,7 @@ class driver(dectalkDriver.driver):
data,
description,
write_lock=None,
cancel_generation=None,
):
if not data:
return
@@ -157,6 +212,15 @@ class driver(dectalkDriver.driver):
try:
total_written = 0
while total_written < len(data):
if self._speech_cancel_requested(cancel_generation):
self._debug(
"DECtalk USB stopped speech write after "
f"{total_written} bytes because speech was "
"cancelled",
debug.DebugLevel.INFO,
on_any_level=True,
)
return
chunk = data[
total_written : total_written + self.packet_size
]
@@ -186,6 +250,19 @@ class driver(dectalkDriver.driver):
on_any_level=True,
)
def _advance_speech_cancel_generation(self):
with self._speech_cancel_lock:
self._speech_cancel_generation += 1
def _get_speech_cancel_generation(self):
with self._speech_cancel_lock:
return self._speech_cancel_generation
def _speech_cancel_requested(self, cancel_generation):
if cancel_generation is None:
return False
return cancel_generation != self._get_speech_cancel_generation()
def _dispose_usb_device(self, usb_device):
try:
self.usb_util.dispose_resources(usb_device)