Commit graph

288 commits

Author SHA1 Message Date
donovanmalisch23-alt
f266faaff7 espeak: escape XML-special characters in single-character mode
When speakup echoes a single character, espeakup wraps it in SSML:

    <say-as interpret-as="characters">%c</say-as>

If the character is one of XML's five special characters (< > & ' "),
the raw byte is injected straight into the markup, producing ill-formed
SSML.  espeak-ng then misparses the <say-as> element, which surfaces as
a spurious high-pitched "ringing" whenever one of these characters is
spoken.

Escape each special character to its corresponding XML entity
(&lt; &gt; &amp; &apos; &quot;) so the markup stays well-formed.
Non-printable characters (< 0x20 or > 0x7e) are no longer wrapped in
SSML either; they fall through to the existing raw-synthesis fallback
so they cannot corrupt the surrounding element either.

The space and ordinary-printable-character paths are unchanged.
2026-08-03 20:52:35 +02:00
Achill Gilgenast
c3c7c6a007 services: add fallback systemd unitdir if systemd is not available
Allows building the service files without a systemd dependency by
defining a default systemd user unit directory.

Relevant in Alpine, where we can package the service files in a
subpackage without having systemd in Alpine.

Addresses feedback of https://github.com/linux-speakup/espeakup/pull/64,
therefore superseeds it.
2026-06-29 18:55:16 +02:00
Alexander Epaneshnikov
cc1447b16a fixup! espeak: detect a wedged engine, restart it, and eventually give up 2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
76a2af5008 fixup! espeak: back off before retrying after any espeak error 2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
6835449642 fixup! softsynth: bound the wait for espeak to acknowledge a stop request 2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
71d0e1fa2a espeak: detect a wedged engine, restart it, and eventually give up
Issue #62 reports espeakup going permanently silent after libespeak-ng
prints "error: Device or resource busy" (EBUSY from the ALSA device,
via pcaudiolib).  Once the audio output is wedged, espeak's internal
command queue never drains, every espeak_Synth call fails with
EE_BUFFER_FULL forever, and espeakup just kept retrying silently.

EE_BUFFER_FULL is also perfectly normal while a long backlog is being
played back, so persistent failure alone is not a reliable signal.  To
tell a backlogged engine from a wedged one, note progress whenever the
synth callback fires (it is invoked for every chunk espeak
synthesizes, and synthesis is paced by audio playback): if entries
keep failing for ~10 seconds with no callback activity at all, declare
the engine wedged.

Recovery is layered:
- restart the engine in-process (espeak_Cancel + espeak_Terminate +
  reinitialize), which recovers transient device problems;
- if the engine has not been healthy for at least a minute between
  such restarts, after 3 restarts give up and exit, letting the init
  system (Restart=always in our systemd unit) respawn espeakup in a
  completely clean state;
- if the restart itself blocks on the wedged device, the
  stop-acknowledgement timeout in the softsynth thread eventually
  terminates the process as a last resort.

A quick espeak_Synth success right after a restart does not count as
healthy on purpose: espeak's freshly emptied internal queue accepts
entries even while the device is still wedged.

Helps: https://github.com/linux-speakup/espeakup/issues/45
Helps: https://github.com/linux-speakup/espeakup/issues/62

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
a504688f3d espeak: do not call espeak functions after a failed reinitialization
When resuming from CMD_PAUSE, queue_process_entry ignored the result
of reinitialize_espeak: if espeak_Initialize failed, paused_espeak
remained set, yet the entry was processed anyway, calling
espeak_Synth & co on a terminated engine.  Combined with the
busy-retry loop, this produced an endless stream of failing calls
against a dead engine.

Make reinitialize_espeak report failure, and when espeak is
unavailable, leave the entry queued and back off before trying to
reinitialize again.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
7405455baf espeak: back off before retrying after any espeak error
When processing an entry failed, only EE_BUFFER_FULL throttled before
the retry; any other persistent error (e.g. EE_INTERNAL_ERROR after
espeak was terminated) made queue_process_entry retry the same entry
in a tight loop with no sleep, burning a whole CPU while printing to a
stderr that points to /dev/null in daemon mode.

Factor the one-second throttle out into espeak_wait_retry() and apply
it to every failed entry.  The wake_stop condition variable still
interrupts the wait immediately when a flush comes in.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
e92edf96dc softsynth: bound the wait for espeak to acknowledge a stop request
request_espeak_stop() waited forever for the espeak thread to
acknowledge the stop.  If espeak-ng is wedged inside the audio output
(e.g. an ALSA device blocked or stuck returning EBUSY, as reported in
issue #62), the acknowledgement never comes, and the softsynth thread
stops draining /dev/softsynth forever.  Speakup's kernel buffer then
fills up and console output stalls, which matches the "blocks dmesg
output after a couple of pages" observation in issue #45.  The process
goes silent and only SIGKILL gets rid of it.

Wait at most 10 seconds for the acknowledgement (a normal cancellation
takes milliseconds; the timeout can only trigger when espeak is truly
stuck).  On timeout, exit with a clear message so that the init system
respawns espeakup in a clean state: our systemd unit already has
Restart=always.  A one-second restart beats an unkillable silent
daemon, and was explicitly requested by the reporter of issue #62.

Helps: https://github.com/linux-speakup/espeakup/issues/45
Helps: https://github.com/linux-speakup/espeakup/issues/62

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
2b959e874e signal: wake all condition-variable waiters on shutdown
On SIGINT/SIGTERM, the signal thread only set should_run to 0 and
relied on a wake-up chain to propagate the shutdown: the self-pipe
wakes the softsynth thread out of select(), which on exit signals
runner_awake to wake the espeak thread.

That chain breaks whenever the softsynth thread is not sitting in
select() but waiting on stop_acknowledged in request_espeak_stop():
nobody ever signals that condition variable on shutdown, so the thread
never re-evaluates should_run and the process never exits.

Broadcast all three condition variables after clearing should_run, so
that every parked thread re-checks its predicate, whichever wait it is
blocked in.

Helps: https://github.com/linux-speakup/espeakup/issues/45
Helps: https://github.com/linux-speakup/espeakup/issues/62

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 10:29:36 +02:00
Alexander Epaneshnikov
d1f7e2384b espeak: do not call espeak_Cancel() while holding queue_guard
When a flush is requested, the espeak thread called stop_speech() ->
espeak_Cancel() with queue_guard held.  espeak_Cancel() waits for
espeak-ng's internal say thread to acknowledge the cancellation, and
that thread can be blocked indefinitely inside a blocking ALSA call
(snd_pcm_writei/snd_pcm_drain on a wedged device, as seen with EBUSY
errors).  In that case queue_guard was held forever, which in turn:

- blocked the signal thread on pthread_mutex_lock(), so SIGINT/SIGTERM
  appeared to be ignored and only SIGKILL could end the process;
- left the softsynth thread stuck in request_espeak_stop(), so
  /dev/softsynth was no longer drained, speakup's kernel buffer filled
  up, and console output (e.g. dmesg) stalled.

Release queue_guard around the espeak_Cancel() call.  This is safe
because the only queue producer, the softsynth thread, is blocked
waiting for stop_acknowledged for as long as stop_requested is set, so
the queue cannot be mutated concurrently.

Helps: https://github.com/linux-speakup/espeakup/issues/45
Helps: https://github.com/linux-speakup/espeakup/issues/62

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 10:29:36 +02:00
Copilot
dc1f1b9efa
Fix deprecated Meson setup command warning in README (#61)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: alex19EP <4889846+alex19EP@users.noreply.github.com>
2025-08-22 14:12:32 +03:00
Samuel Thibault
fa848f509e set_punctuation: Fix punctuation levels values
They do not actually follow the espeak values (and have no reason to,
anyway).
2025-05-29 01:00:18 +03:00
Samuel Thibault
98dc103749 queue_process_entry: Avoid leaving error uninitialized
CMD_PAUSE was not actually setting error to EE_OK, and the default case,
even if it is not supposed to happen, should set error to something sane.
2025-05-29 00:59:51 +03:00
Alexander Epaneshnikov
d7c06a6f9f
meson: switch from deprecated functions (#49) 2022-03-11 14:51:28 +01:00
Samuel Thibault
108c28ae08 Throttle on EE_BUFFER_FULL errors
When espeak returns EE_BUFFER_FULL we should just wait a bit before
retrying. Also, we don't want to lose the current entry. We however want
to wake up as soon as possible on stop request.
2022-03-03 05:53:04 +03:00
Brandon McGinty
78e561fae9 use correct case for --pid-file option
This permits --pid-file to be specified.
The getopt_long option was looking for a lowercase p,
and the pid-file option used an uppercase P.
This meant that pid-file was never settable.
2022-02-14 16:14:23 +03:00
Samuel Thibault
c99bfb8e45 Request systemd to protect espeakup from OOM
Otherwise it might get killed on memory pressure.
2022-02-10 18:37:23 -06:00
Samuel Thibault
ca234f2122 Request systemd to prioritize espeakup
So speech doesn't get choppy on a loaded system.

As suggested by Nick Gawronski.

This fixes #46.
2022-02-10 18:30:08 -06:00
Samuel Thibault
316e4fc51d softsynth: on error, be clear we are talking about /dev/softsynth 2021-08-21 16:30:32 +03:00
William Hubbs
e858481c0a use dupeString in command line processing 2021-07-07 22:16:25 -05:00
William Hubbs
8f18ad7f88 add dupeString wrapper function
This function calls strdup and exits on failure.
2021-07-07 22:16:25 -05:00
William Hubbs
b7282c1a90 use systemd modprobe service for speakup_soft 2021-07-03 23:37:00 +03:00
William Hubbs
f077d0c042 format build files consistently 2021-07-03 03:53:58 +03:00
Christopher Brannon
3794e8e74b Update my email address in the docs. 2021-06-20 15:58:20 +03:00
Alexander Epaneshnikov
569e9f5b0e
bump version v0.90 2021-06-19 10:26:15 +03:00
Alexander Epaneshnikov
c50e4eb088
remove command line from README (#33)
now when we have man documentation in markdown, no need to store the same
information in two places.
2021-06-19 10:05:57 +03:00
Alexander Epaneshnikov
e13ceb3cce edit by hand
more formatting improvements
2021-06-19 01:57:49 -05:00
Alexander Epaneshnikov
c528913ad9 format code 2021-06-19 01:57:49 -05:00
Alexander Epaneshnikov
dd2dcb68bf use clang-format for code styling 2021-06-19 01:57:49 -05:00
Alexander Epaneshnikov
ca1d6b42e2 generate man page from markdown
Use ronn to convert markdown to a man page
This simplifies maintaining the documentation.

This fixes #31.
2021-06-19 01:34:18 -05:00
Alexander Epaneshnikov
0c09fe5449
switch to meson (#30)
switch to a meson-based build system
2021-06-18 21:53:04 -05:00
William Hubbs
30ef2145e7 remove TODO
This file is no longer needed since we have an official bug tracker.
2021-06-15 09:19:43 +03:00
William Hubbs
2d8d2c55c0 rename autostart directory to services 2021-06-15 09:19:18 +03:00
Alexander Epaneshnikov
c69d98cde6 clean makefile
get rid of unnecessary targets. add warning options.
2021-06-15 06:08:50 +03:00
Alexander Epaneshnikov
2f3d171863 update issue url 2021-06-15 06:08:50 +03:00
Alexander Epaneshnikov
512a958dad remove changelog
we can do better than that.
2021-06-15 06:08:50 +03:00
Alexander Epaneshnikov
335d748a63 new src structure
to make it more understandable and convenient for further improvements.
2021-06-15 06:08:50 +03:00
Alexander Epaneshnikov
3fb775cc33 load speakup_soft kernel module 2021-06-14 22:14:47 +03:00
Alexander Epaneshnikov
6689948a8a add espeakup manual in unit file 2021-06-14 22:14:47 +03:00
Alexander Epaneshnikov
b5c1aef849 add systemd unit 2021-06-14 22:14:47 +03:00
Alexander Epaneshnikov
201ae667ee
link with espeak-ng by default (#25) 2021-06-14 20:45:53 +02:00
William Hubbs
a464461f0e convert README to markdown 2021-06-14 21:12:33 +03:00
William Hubbs
9ccabf55b6 update README
- Remove the unnecessary references to tarballs since this is now a
  standard github project.

- Point people to the bug tracker for filing bugs.
2021-06-14 21:12:33 +03:00
Samuel Thibault
ee05f278f2 Add support for indexing 2021-06-14 20:47:04 +03:00
Alexander Epaneshnikov
70ae4dece7 enlaarge voice buf
this will fix #9
2021-06-14 20:41:42 +03:00
Samuel Thibault
171bb517a0 Let espeak choose the buffer size
The default is already quite small, and choosing a too small one may
lead to broken speech.

Fixes #13
2021-06-14 20:37:53 +03:00
Alexander Epaneshnikov
7159441731
Merge pull request #19 from sthibaul/alsa-volume
Support setting ALSA volume in addition to espeak volume
2021-06-14 20:09:19 +03:00
Alexander Epaneshnikov
7cfba0878b
Merge pull request #18 from sthibaul/range
Support pitch range configuration
2021-06-14 20:01:52 +03:00
Alexander Epaneshnikov
1c3285d249
Merge pull request #15 from sthibaul/pause
pass '\n' to espeak too for e.g. proper pause
2021-06-14 19:53:15 +03:00