Compare commits

...

59 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 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
Samuel Thibault
53665b8eab Support setting ALSA volume in addition to espeak volume
This allows to make sure to reach the maximum volume permitted by the
hardware. This makes the default volume (5) set ALSA volume to 80%, like
the default ALSA scripts do.

This is not enabled by default, since users will probably want to use a
mixer to fine-tune their volume. But for e.g. installation images, this
allows to spare the use of a mixer and just use the speakup volume
control.
2020-04-29 02:48:39 +02:00
Samuel Thibault
a9657bbb2b Support pitch range configuration
This allows to let users choose expressiveness of their synth.
2020-04-25 21:47:00 +02:00
Samuel Thibault
1e31008091 pass '\n' to espeak too for e.g. proper pause 2019-08-18 20:30:45 +02:00
Samuel Thibault
e69d61b2d8 signal: Add missing mutex_lock/unlock around the while loop
This fixes #12.
2018-07-11 16:32:42 -05:00
Samuel Thibault
5f01999726 Make empty voice name select the default voice
See http://bugs.debian.org/872194
This fixes #11.
2018-07-11 16:14:52 -05:00
Samuel Thibault
5339da3144 Support audio pauses
When the Linux console is e.g. switched to a graphical VT, the kernel
emits \x01P to notify that it is not able to read the screen any more,
and the software synthesis can thus release the audio card, for other
screen readers to take over.

This implements it by adding a paused_espeak variable that tracks
whether we have suspended espeak.  When more text comes, we can simply
reinitialize espeak.

This fixes #10.
2018-07-11 16:02:44 -05:00
Samuel Thibault
a5b655ddb0 fix speaking spaces
Espeak doesn't speak spaces unless it is specifically told to do so.
2017-03-14 21:56:31 -05:00
Samuel Thibault
b7fe3af320 add unicode variant of /dev/softsynth 2017-03-14 21:52:14 -05:00
William Hubbs
e21b746eb9 version 0.81 2017-03-14 21:45:26 -05:00
28 changed files with 1098 additions and 2423 deletions

41
.clang-format Normal file
View file

@ -0,0 +1,41 @@
---
Language: Cpp
BasedOnStyle: LLVM
AlignAfterOpenBracket: Align
AlignEscapedNewlines: Left
AlignOperands: AlignAfterOperator
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
BinPackArguments: true
BinPackParameters: true
BreakBeforeBraces: Linux
ColumnLimit: 80
IndentPPDirectives: BeforeHash
IndentWidth: 4
InsertTrailingCommas: None
KeepEmptyLinesAtTheStartOfBlocks: false
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Before
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 5
TabWidth: 4
UseTab: AlignWithSpaces
...

5
.gitignore vendored
View file

@ -1,4 +1 @@
espeakup
*.d
*.o
*.swp
build*

1806
ChangeLog

File diff suppressed because it is too large Load diff

View file

@ -1,45 +0,0 @@
PREFIX = /usr/local
BINDIR = ${PREFIX}/bin
MANDIR = ${PREFIX}/share/man
DEPFLAGS = -MMD
WARNFLAGS = -Wall
CFLAGS += ${DEPFLAGS} ${WARNFLAGS}
LDLIBS = -lespeak -lpthread
INSTALL = install
BINMODE = 0755
MANMODE = 0644
CHANGELOG_LIMIT?= --after="1 year ago"
SRCS = cli.c \
espeak.c \
espeakup.c \
queue.c \
signal.c \
softsynth.c \
stringhandling.c
OBJS = ${SRCS:.c=.o}
all: espeakup
changelog:
git log ${CHANGELOG_LIMIT} --format=full > ChangeLog
install: espeakup
${INSTALL} -d ${DESTDIR}${BINDIR}
${INSTALL} -m ${BINMODE} $< ${DESTDIR}${BINDIR}
${INSTALL} -d ${DESTDIR}${MANDIR}/man8
${INSTALL} -m ${MANMODE} espeakup.8 ${DESTDIR}${MANDIR}/man8
espeakup: ${OBJS}
clean:
${RM} *.d *.o
distclean: clean
${RM} espeakup
-include ${SRCS:.c=.d}

70
README
View file

@ -1,70 +0,0 @@
espeakup connector
=======================
espeakup is a program which makes it possible for speakup to use
the espeak software synthesizer. It does this by reading speakup's
softsynth device and passing the text to espeak which actually speaks.
Requirements
============
This program works with the speakup screen reader, which can be obtained
from http://linux-speakup.org, and the espeak software speech
synthesizer which can be obtained from http://espeak.sourceforge.net.
You must have both of these installed and operational. Setting them up
is beyond the scope of this document.
Installation
============
The preferred way to install espeakup is using your distribution's
packaging system, but if your distribution does not have a package for
espeakup yet, espeakup just uses a Makefile, so you should be able to
change to the source directory, then type make, then as root, make
install.
Starting Up
===========
This program should be run after speakup is set up to communicate with a
software synthesizer and after /dev/softsynth exists. The way this is
done is distribution specific, so it is beyond the scope of this
documentation.
Command Line Options
====================
Espeakup currently accepts the following command line options:
--default-voice=voice, -V voice Set default voice.
--debug, -d Debug mode (stay in the foreground).
--help, -h Show this help.
--version, -v Display the software version.
Getting the Latest Version
==========================
It is possible to download a tarball from github of any released version
as follows:
wget http://www.github.com/williamh/espeakup/tarball/vx.y
If you need a tarball for packaging purposes, one is available from
ftp://ftp.linux-speakup.org/pub/linux/goodies/espeakup-x.y.tar.bz2.
The url for the git repository is git://github.com/williamh/espeakup.git.
Acknowledgements
================
I would like to thank Marc Mulcahy, the author of the speakup to
TTSynth connector, on which this work is based. Also, I would like
to thank Kirk Reiser and Jonathan Duddington, the
authors of Speakup and Espeak, respectively, for their work.
Questions
=========
You can contact me with questions, bugs, patches, etc, at
w.d.hubbs@gmail.com or on the speakup mailing list. I hope you find
this software to be useful.

48
README.md Normal file
View file

@ -0,0 +1,48 @@
# espeakup connector
espeakup is a program which makes it possible for speakup to use
the espeak-ng software synthesizer. It does this by reading speakup's
softsynth device and passing the text to espeak-ng which actually speaks.
## Requirements
This program works with the speakup screen reader, which can be obtained
from http://linux-speakup.org, and the
[espeak-ng](https://github.com/espeak-ng/espeak-ng) software speech
synthesizer.
You must have both of these installed and operational. Setting them up
is beyond the scope of this document.
## Installation
The preferred way to install espeakup is using your distribution's
packaging system, but if your distribution does not have a package for
espeakup yet, espeakup just uses meson, so you should be able to
change to the source directory, then type:
```bash
meson setup . ./build
cd ./build
ninja
sudo ninja install
```
## Starting Up
This program should be run after speakup is set up to communicate with a
software synthesizer and after /dev/softsynth exists. The way this is
done is distribution specific, so it is beyond the scope of this
documentation.
## Acknowledgements
I would like to thank Marc Mulcahy, the author of the speakup to
TTSynth connector, on which this work is based. Also, I would like
to thank Kirk Reiser and Jonathan Duddington, the
authors of Speakup and Espeak, respectively, for their work.
## Filing Bugs
Bugs should be filed on our bug tracker at
https://github.com/linux-speakup/issues.

11
TODO
View file

@ -1,11 +0,0 @@
This file contains a list of improvements that need to be made to
espeakup.
Voice and language support should be added at some point.
This will consist of the following:
- add a function which will allow switching voices. (completed on 9/27)
- add a function/command which will allow speakup to find out which
voices espeak supports.
- define and add the command to speakup which will allow it to switch
voices.

77
doc/espeakup.8.ronn Normal file
View file

@ -0,0 +1,77 @@
<!-- markdownlint-disable MD036 -->
# espeakup(8) --- connect Speakup to the espeak-ng TTS engine
## SYNOPSIS
`espeakup` [`--pid-path=`<path>] [`--alsa-volume`]
[`--default-voice=`[<voicename>]] [`--debug`] [`--help`] [`--version`]
## OPTIONS
* `-P` <path>, `--pid-path=`<path>:
Set the full path for the pid file espeakup uses when in daemon mode.
* `--alsa-volume`:
Drive the ALSA volume. useful for live environments where volume
adjustments maybe impossible.
* `-V` <voicename>, `--default-voice=`<voicename>:
Set the espeak-ng voice to be used by default.
* `-d`, `--debug`:
run in the foreground, rather than becoming a daemon process.
* `-h`, `--help`:
display a brief help message and exit.
* `-v`, `--version`:
output version information and exit.
## DESCRIPTION
espeakup bridges the gap between two tools: the Speakup screen review system and
the espeak-ng text-to-speech engine. Each of these tools performs a
well-defined task.
Speakup is a kernel-based screen reader for the Linux console. It extracts and
processes the text that is displayed on the foreground virtual console. It
supports several hardware based speech synthesizers directly. However, since it
is in kernel space, it cannot support a software speech synthesizer directly
since these are in user space.
espeak-ng is a popular software speech synthesizer. It is small, light weight,
very responsive, and supports multiple languages. espeakup is a connector which
will read text sent to it by speakup and forward it to espeak-ng. This allows
Speakup to use espeak-ng as its speech synthesizer.
espeakup is a daemon. Typically, it is started at boot time, and it terminates
when the system is halted or rebooted. It should be started by the system's init
scripts. This process varies among Linux distributions, but the details are
usually managed by the person who packaged espeakup for your distribution. From
the perspective of an average user, espeakup's operation is invisible.
## BUGS
If you find a bug, please create a
[github issue](https://github.com/linux-speakup/espeakup/issues)
You might also consider mentioning it on the mailing list for the Speakup
screenreader. Visit
[list page](https://linux-speakup.org/cgi-bin/mailman/listinfo/speakup)
to learn more about the mailing list.
## SEE ALSO
For more information about Speakup, visit its
[homepage](https://linux-speakup.org).
espeak-ng can be found at [github](https://github.com/espeak-ng/espeak-ng)
## AUTHOR
William Hubbs <w.d.hubbs@gmail.com> is the author of espeakup.
This manual page was written by Chris Brannon <chris@the-brannons.com>.
current authors and maintainers can be found at
[github](https://github.com/linux-speakup/espeakup/graphs/contributors)

8
doc/meson.build Normal file
View file

@ -0,0 +1,8 @@
ronn = find_program('ronn', required: get_option('man'))
if ronn.found()
custom_target('man',
input : files('espeakup.8.ronn'),
output : 'espeakup.8',
command : [ronn, '--output-dir', '@OUTDIR@', '--roff', '@INPUT@'],
install : true, install_dir: join_paths(get_option('mandir'),'man8'))
endif

328
espeak.c
View file

@ -1,328 +0,0 @@
/*
* espeakup - interface which allows speakup to use espeak
*
* Copyright (C) 2008 William Hubbs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "espeakup.h"
/* default voice settings */
const int defaultFrequency = 5;
const int defaultPitch = 5;
const int defaultRate = 2;
const int defaultVolume = 5;
char *defaultVoice = NULL;
/* multipliers and offsets */
const int frequencyMultiplier = 11;
const int pitchMultiplier = 11;
const int rateMultiplier = 41;
const int rateOffset = 80;
const int volumeMultiplier = 22;
volatile int stop_requested = 0;
static int acsint_callback(short *wav, int numsamples, espeak_EVENT * events)
{
int i;
for (i = 0; events[i].type != espeakEVENT_LIST_TERMINATED; i++) {
if (events[i].type == espeakEVENT_MARK) {
int mark = atoi(events[i].id.name);
if ((mark < 0) || (mark > 255))
continue;
putchar(mark);
fflush(stdout);
}
}
return 0;
}
static espeak_ERROR set_frequency(struct synth_t *s, int freq,
enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
freq = -freq;
if (adj != ADJ_SET)
freq += s->frequency;
rc = espeak_SetParameter(espeakRANGE, freq * frequencyMultiplier, 0);
if (rc == EE_OK)
s->frequency = freq;
return rc;
}
static espeak_ERROR set_pitch(struct synth_t *s, int pitch,
enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
pitch = -pitch;
if (adj != ADJ_SET)
pitch += s->pitch;
rc = espeak_SetParameter(espeakPITCH, pitch * pitchMultiplier, 0);
if (rc == EE_OK)
s->pitch = pitch;
return rc;
}
static espeak_ERROR set_punctuation(struct synth_t *s, int punct,
enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
punct = -punct;
if (adj != ADJ_SET)
punct += s->punct;
rc = espeak_SetParameter(espeakPUNCTUATION, punct, 0);
if (rc == EE_OK)
s->punct = punct;
return rc;
}
static espeak_ERROR set_rate(struct synth_t *s, int rate,
enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
rate = -rate;
if (adj != ADJ_SET)
rate += s->rate;
rc = espeak_SetParameter(espeakRATE,
rate * rateMultiplier + rateOffset, 0);
if (rc == EE_OK)
s->rate = rate;
return rc;
}
static espeak_ERROR set_voice(struct synth_t *s, char *voice)
{
espeak_ERROR rc;
espeak_VOICE voice_select;
rc = espeak_SetVoiceByName(voice);
if (rc != EE_OK)
{
memset(&voice_select, 0, sizeof(voice_select));
voice_select.languages = voice;
rc = espeak_SetVoiceByProperties(&voice_select);
}
if (rc == EE_OK)
strcpy(s->voice, voice);
return rc;
}
static espeak_ERROR set_volume(struct synth_t *s, int vol,
enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
vol = -vol;
if (adj != ADJ_SET)
vol += s->volume;
rc = espeak_SetParameter(espeakVOLUME, (vol + 1) * volumeMultiplier,
0);
if (rc == EE_OK)
s->volume = vol;
return rc;
}
static espeak_ERROR stop_speech(void)
{
espeak_ERROR rc;
rc = espeak_Cancel();
return rc;
}
static espeak_ERROR speak_text(struct synth_t *s)
{
espeak_ERROR rc;
int synth_mode = 0;
if (espeakup_mode == ESPEAKUP_MODE_ACSINT)
synth_mode |= espeakSSML;
if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) {
char *buf;
int n;
n = asprintf(&buf,
"<say-as interpret-as=\"characters\">%c</say-as>",
s->buf[0]);
if (n == -1) {
/* D'oh. Not much to do on allocation failure.
* Perhaps espeak will happen to say the character */
rc = espeak_Synth(s->buf, s->len + 1, 0, POS_CHARACTER,
0, synth_mode, NULL, NULL);
} else {
rc = espeak_Synth(buf, n + 1, 0, POS_CHARACTER, 0,
espeakSSML, NULL, NULL);
free(buf);
}
} else
rc = espeak_Synth(s->buf, s->len + 1, 0, POS_CHARACTER, 0,
synth_mode, NULL, NULL);
return rc;
}
static void free_espeak_entry(struct espeak_entry_t *entry)
{
assert(entry);
if (entry->cmd == CMD_SPEAK_TEXT)
free(entry->buf);
free(entry);
}
static void synth_queue_clear()
{
struct espeak_entry_t *current;
while (queue_peek(synth_queue)) {
current = (struct espeak_entry_t *) queue_remove(synth_queue);
free_espeak_entry(current);
}
}
static void queue_process_entry(struct synth_t *s)
{
espeak_ERROR error;
static struct espeak_entry_t *current = NULL;
if (current != queue_peek(synth_queue)) {
if (current)
free_espeak_entry(current);
current = (struct espeak_entry_t *) queue_remove(synth_queue);
}
pthread_mutex_unlock(&queue_guard);
switch (current->cmd) {
case CMD_SET_FREQUENCY:
error = set_frequency(s, current->value, current->adjust);
break;
case CMD_SET_PITCH:
error = set_pitch(s, current->value, current->adjust);
break;
case CMD_SET_PUNCTUATION:
error = set_punctuation(s, current->value, current->adjust);
break;
case CMD_SET_RATE:
error = set_rate(s, current->value, current->adjust);
break;
case CMD_SET_VOICE:
error = EE_OK;
break;
case CMD_SET_VOLUME:
error = set_volume(s, current->value, current->adjust);
break;
case CMD_SPEAK_TEXT:
s->buf = current->buf;
s->len = current->len;
error = speak_text(s);
break;
default:
break;
}
if (error == EE_OK) {
free_espeak_entry(current);
current = NULL;
}
}
int initialize_espeak(struct synth_t *s)
{
int rate;
/* initialize espeak */
rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 50, NULL, 0);
if (rate < 0) {
fprintf(stderr, "Unable to initialize espeak.\n");
return -1;
}
/* We need a callback in acsint mode, but not in speakup mode. */
if (espeakup_mode == ESPEAKUP_MODE_ACSINT)
espeak_SetSynthCallback(acsint_callback);
/* Setup initial voice parameters */
if (defaultVoice) {
set_voice(s, defaultVoice);
free(defaultVoice);
defaultVoice = NULL;
}
set_frequency(s, defaultFrequency, ADJ_SET);
set_pitch(s, defaultPitch, ADJ_SET);
set_rate(s, defaultRate, ADJ_SET);
set_volume(s, defaultVolume, ADJ_SET);
espeak_SetParameter(espeakCAPITALS, 0, 0);
return 0;
}
/* espeak_thread is the "main" function of our secondary (queue-processing)
* thread.
* First, lock queue_guard, because it needs to be locked when we call
* pthread_cond_wait on the runner_awake condition variable.
* Next, enter an infinite loop.
* The wait call also unlocks queue_guard, so that the other thread can
* manipulate the queue.
* When runner_awake is signaled, the pthread_cond_wait call re-locks
* queue_guard, and the "queue processor" thread has access to the queue.
* While there is an entry in the queue, call queue_process_entry.
* queue_process_entry unlocks queue_guard after removing an item from the
* queue, so that the main thread doesn't have to wait for us to finish
* processing the entry. So re-lock queue_guard after each call to
* queue_process_entry.
*
* The main thread can add items to the queue in exactly two situations:
* 1. We are waiting on runner_awake, or
* 2. We are processing an entry that has just been removed from the queue.
*/
void *espeak_thread(void *arg)
{
struct synth_t *s = (struct synth_t *) arg;
pthread_mutex_lock(&queue_guard);
while (should_run) {
while (should_run && !queue_peek(synth_queue) && !stop_requested)
pthread_cond_wait(&runner_awake, &queue_guard);
if (stop_requested) {
stop_speech();
synth_queue_clear();
stop_requested = 0;
pthread_cond_signal(&stop_acknowledged);
}
while (should_run && queue_peek(synth_queue) && !stop_requested) {
queue_process_entry(s);
pthread_mutex_lock(&queue_guard);
}
}
pthread_cond_signal(&stop_acknowledged);
pthread_mutex_unlock(&queue_guard);
return NULL;
}

View file

@ -1,78 +0,0 @@
.\" Hey, Emacs! This is an -*- nroff -*- source file.
.\" Espeakup is Copyright 2008 by William Hubbs.
.\" This is free software; see the GNU General Public Licence version 3
.\" or later for copying conditions. There is NO warranty.
.TH ESPEAKUP 8 "5 Nov 2008" "0.60"
.nh
.SH NAME
espeakup \(em connect Speakup to the ESpeak TTS engine
.SH SYNOPSIS
.B espeakup
[
.B \-\^\-pid-path=path
]
[
.B \-\^\-default-voice=voicename
]
[
.B \-\^\-debug
]
[
.B \-\^\-help
]
[
.B \-\^\-version
]
.SH OPTIONS
.TP
.B \-P path, \-\^\-pid-path=path
Set the full path for the pid file espeakup uses when in daemon mode.
.TP
.B \-V voicename, \-\^\-default-voice=voicename
Set the espeak voice to be used by default.
.TP
.B \-d, \-\^\-debug
run in the foreground, rather than becoming a daemon process.
.TP
.B \-h, \-\^\-help
display a brief help message and exit.
.TP
.B \-v, \-\^\-version
output version information and exit.
.SH DESCRIPTION
Espeakup bridges the gap between two tools: the Speakup screen review
system and the ESppeak text-to-speech engine. Each of these tools
performs a well-defined task. Speakup is a kernel-based screen reader
for the Linux console. It extracts and processes the text that is
displayed on the foreground virtual console. It supports several
hardware based speech synthesizers directly. However, since it is in
kernel space, it cannot support a software speech synthesizer directly
since these are in user space.
ESpeak is a popular software speech synthesizer. It is small, light
weight, very responsive, and supports multiple languages.
Espeakup is a connector which will read text sent to it by speakup and
forward it to ESpeak. This allows Speakup to use ESpeak as its speech
synthesizer.
.PP
Espeakup is a daemon. Typically, it is started at boot time, and it terminates
when the system is halted or rebooted. It should be started by the
system's init scripts. This process varies among Linux distributions,
but the details are usually managed by the person who packaged Espeakup for
your distribution.
From the perspective of an average user, Espeakup's operation is invisible.
.SH BUGS
.PP
Espeakup is still classified as alpha software. Bugs are periodically found
and fixed. If you find a bug, please do report it to the author. You
might also consider mentioning it on the mailing list for the Speakup
screenreader. Visit http://speech.braille.uwo.ca/mailman/listinfo/speakup
to learn more about the mailing list.
.SH SEE ALSO
.PP
For more information about Speakup, visit its homepage: http://linux-speakup.org.
ESpeak's home page is http://espeak.sourceforge.net.
.SH AUTHOR
.PP
William Hubbs is the author and maintainer of Espeakup. He may be reached
via the email address <w.d.hubbs@gmail.com>. This manual page was written
by Chris Brannon, and his email address is <cmbrannon79@gmail.com>.

25
meson.build Normal file
View file

@ -0,0 +1,25 @@
project('espeakup', 'c',
default_options : [
'buildtype=debugoptimized',
'c_std=gnu11',
'warning_level=3'
],
license : 'GPL-3.0-or-later',
version : '0.90',
meson_version : '>=0.51.0')
cc = meson.get_compiler('c')
thread_dep = dependency('threads')
espeak_dep = dependency('espeak-ng')
alsa_dep = dependency('alsa')
math_dep = cc.find_library('m', required : false)
subdir('doc')
subdir('services')
subdir('src')
executable('espeakup',
espeakup_version,
espeakup_sources,
dependencies : [thread_dep, espeak_dep, alsa_dep, math_dep],
install : true)

4
meson_options.txt Normal file
View file

@ -0,0 +1,4 @@
option('man', type : 'feature', value : 'auto',
description : 'build manpage with ronn')
option('systemd', type : 'feature', value : 'auto',
description :'enable systemd support')

4
services/meson.build Normal file
View file

@ -0,0 +1,4 @@
systemd = dependency('systemd')
if (systemd.found() and get_option('systemd').allowed()) or get_option('systemd').enabled()
subdir('systemd')
endif

View file

@ -0,0 +1,18 @@
[Unit]
Description=Software speech output for Speakup
Documentation=man:espeakup(8)
Wants=modprobe@speakup_soft.service
After=modprobe@speakup_soft.service sound.target
[Service]
Type=forking
PIDFile=/run/espeakup.pid
Environment="default_voice="
ExecStart=@bindir@/espeakup --default-voice=${default_voice}
ExecReload=kill -HUP $MAINPID
Restart=always
Nice=-10
OOMScoreAdjust=-900
[Install]
WantedBy=sound.target

View file

@ -0,0 +1,20 @@
if systemd.found()
unitdir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir')
else
unitdir = join_paths(prefixdir, get_option('libdir'), 'systemd', 'system')
endif
prefixdir = get_option('prefix')
bindir = join_paths(prefixdir, get_option('bindir'))
unit_conf = configuration_data()
unit_conf.set('bindir', bindir)
service_file = configure_file(
input : 'espeakup.service.in',
output : 'espeakup.service',
configuration : unit_conf
)
install_data(service_file,
install_dir : unitdir
)

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
@ -23,6 +23,8 @@
#include <string.h>
#include "espeakup.h"
#include "stringhandling.h"
#include "version.h"
/* pid path */
extern char *pidPath;
@ -30,17 +32,20 @@ extern char *pidPath;
/* default voice */
extern char *defaultVoice;
/* Whether to drive ALSA volume */
extern int alsaVolume;
/* command line options */
const char *shortOptions = "P:V:adhv";
const struct option longOptions[] = {
{"pid-path", required_argument, NULL, 'P'},
{"default-voice", required_argument, NULL, 'V'},
{"alsa-volume", no_argument, &alsaVolume, 1},
{"acsint", no_argument, NULL, 'a'},
{"debug", no_argument, NULL, 'd'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{0, 0, 0, 0}
};
{0, 0, 0, 0}};
static void show_help()
{
@ -48,6 +53,7 @@ static void show_help()
printf("Options are as follows:\n");
printf(" --pid-path=path, -P path\t\tSet path for pid file.\n");
printf(" --default-voice=voice, -V voice\tSet default voice.\n");
printf(" --alsa-volume\t\t\t\tDrive the ALSA volume.\n");
printf(" --debug, -d\t\t\t\tDebug mode (stay in the foreground).\n");
printf(" --help, -h\t\t\t\tShow this help.\n");
printf(" --version, -v\t\t\t\tDisplay the software version.\n");
@ -67,18 +73,15 @@ static void show_version(void)
void process_cli(int argc, char **argv)
{
int opt;
char *cp;
do {
opt = getopt_long(argc, argv, shortOptions, longOptions, NULL);
switch (opt) {
case 'p':
cp = strdup(optarg);
if (cp != NULL)
pidPath = cp;
case 'P':
pidPath = dupeString(optarg);
break;
case 'V':
defaultVoice = strdup(optarg);
defaultVoice = dupeString(optarg);
break;
case 'a':
espeakup_mode = ESPEAKUP_MODE_ACSINT;
@ -93,6 +96,7 @@ void process_cli(int argc, char **argv)
show_version();
break;
case -1:
case 0:
break;
default:
show_help();

638
src/espeak.c Normal file
View file

@ -0,0 +1,638 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <alsa/asoundlib.h>
#include <assert.h>
#include <math.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "espeakup.h"
/* default voice settings */
const int defaultFrequency = 5;
const int defaultPitch = 5;
const int defaultRange = 5;
const int defaultRate = 2;
const int defaultVolume = 5;
char *defaultVoice = NULL;
int alsaVolume = 0;
/* multipliers and offsets */
const int frequencyMultiplier = 11;
const int pitchMultiplier = 11;
const int rangeMultiplier = 11;
const int rateMultiplier = 41;
const int rateOffset = 80;
const int volumeMultiplier = 22;
volatile int stop_requested = 0;
int paused_espeak = 1;
/* Wedged-engine detection. Espeak may legitimately refuse entries for a
* while (EE_BUFFER_FULL while a long backlog is being played back), so a
* failing entry is normally just retried. But when entries keep failing
* while the synth callback reports no progress at all, the audio output
* is most likely wedged (e.g. an ALSA device stuck returning EBUSY).
* After ESPEAK_STALL_RETRIES consecutive such retries (about one second
* each), restart the engine; after ESPEAK_MAX_RESTARTS restarts without
* the engine having been healthy for ESPEAK_HEALTHY_SECS in between,
* give up and exit, so that the init system can respawn us. */
#define ESPEAK_STALL_RETRIES 10
#define ESPEAK_MAX_RESTARTS 3
#define ESPEAK_HEALTHY_SECS 60
/* Set by the synth callback whenever espeak makes synthesis progress;
* used to tell a merely backlogged engine from a wedged one. The
* callback runs in espeak's own thread, so the flag is atomic. */
static atomic_int synth_progressed = 0;
static int stalled_retries = 0;
static int restart_attempts = 0;
static struct timespec last_restart;
static int callback(short *wav, int numsamples, espeak_EVENT *events)
{
int i;
atomic_store(&synth_progressed, 1);
for (i = 0; events[i].type != espeakEVENT_LIST_TERMINATED; i++) {
if (events[i].type == espeakEVENT_MARK) {
int mark = atoi(events[i].id.name);
if ((mark < 0) || (mark > 255))
continue;
softsynth_reportindex(mark);
}
}
return 0;
}
static espeak_ERROR set_frequency(struct synth_t *s, int freq,
enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
freq = -freq;
if (adj != ADJ_SET)
freq += s->frequency;
rc = espeak_SetParameter(espeakRANGE, freq * frequencyMultiplier, 0);
if (rc == EE_OK)
s->frequency = freq;
return rc;
}
static espeak_ERROR set_pitch(struct synth_t *s, int pitch, enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
pitch = -pitch;
if (adj != ADJ_SET)
pitch += s->pitch;
rc = espeak_SetParameter(espeakPITCH, pitch * pitchMultiplier, 0);
if (rc == EE_OK)
s->pitch = pitch;
return rc;
}
static espeak_ERROR set_range(struct synth_t *s, int range, enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
range = -range;
if (adj != ADJ_SET)
range += s->range;
rc = espeak_SetParameter(espeakRANGE, range * rangeMultiplier, 0);
if (rc == EE_OK)
s->range = range;
return rc;
}
static espeak_ERROR set_punctuation(struct synth_t *s, int punct,
enum adjust_t adj)
{
espeak_ERROR rc;
espeak_PUNCT_TYPE espeak_punct;
if (adj == ADJ_DEC)
punct = -punct;
if (adj != ADJ_SET)
punct += s->punct;
switch (punct) {
case 0:
espeak_punct = espeakPUNCT_NONE;
break;
case 1:
espeak_punct = espeakPUNCT_SOME;
break;
case 2:
/* XXX: approximation */
espeak_punct = espeakPUNCT_SOME;
break;
case 3:
default:
espeak_punct = espeakPUNCT_ALL;
break;
}
rc = espeak_SetParameter(espeakPUNCTUATION, espeak_punct, 0);
if (rc == EE_OK)
s->punct = punct;
return rc;
}
static espeak_ERROR set_rate(struct synth_t *s, int rate, enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
rate = -rate;
if (adj != ADJ_SET)
rate += s->rate;
rc = espeak_SetParameter(espeakRATE, rate * rateMultiplier + rateOffset, 0);
if (rc == EE_OK)
s->rate = rate;
return rc;
}
static espeak_ERROR set_voice(struct synth_t *s, char *voice)
{
espeak_ERROR rc;
espeak_VOICE voice_select;
rc = espeak_SetVoiceByName(voice);
if (rc != EE_OK) {
memset(&voice_select, 0, sizeof(voice_select));
voice_select.languages = voice;
rc = espeak_SetVoiceByProperties(&voice_select);
}
if (rc == EE_OK)
strcpy(s->voice, voice);
return rc;
}
static void set_alsa_volume(int vol)
{
snd_mixer_t *m;
snd_mixer_elem_t *e;
int err;
err = snd_mixer_open(&m, 0);
if (err < 0) {
fprintf(stderr, "ALSA mixer open error: %s\n", snd_strerror(err));
return;
}
err = snd_mixer_attach(m, "default");
if (err < 0) {
fprintf(stderr, "ALSA mixer attach error: %s\n", snd_strerror(err));
return;
}
err = snd_mixer_selem_register(m, NULL, NULL);
if (err < 0) {
fprintf(stderr, "ALSA mixer load error: %s\n", snd_strerror(err));
return;
}
err = snd_mixer_load(m);
if (err < 0) {
fprintf(stderr, "ALSA mixer load error: %s\n", snd_strerror(err));
return;
}
/* Turn vol value to volume %.
* We do not want to soften that much with ALSA, espeak is already
* doing it. We want the default value (5) to be the usual default
* volume (80%), and make higher values increase ALSA volume, up to
* 100%. */
int volume = (vol + 1) * 50 / 10 + 50;
for (e = snd_mixer_first_elem(m); e; e = snd_mixer_elem_next(e)) {
if (snd_mixer_elem_get_type(e) != SND_MIXER_ELEM_SIMPLE)
continue;
if (snd_mixer_selem_is_enumerated(e))
continue;
if (snd_mixer_selem_has_playback_switch(e)) {
snd_mixer_selem_set_playback_switch_all(e, 1);
}
if (snd_mixer_selem_has_playback_volume(e)) {
long min, max, set;
err = snd_mixer_selem_get_playback_dB_range(e, &min, &max);
if (err == 0 && min < max) {
if (max - min < 2400) {
/* 24dB amplitude is too small for using a logscale */
set = min + volume * (max - min) / 100;
} else {
/* Use a logscale */
double volf = volume / 100.;
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
double minf = pow(10, (min - max) / 6000.);
volf = volf * (1 - minf) + minf;
}
set = 6000. * log10(volf) + max;
}
snd_mixer_selem_set_playback_dB_all(e, set, 0);
} else {
/* No dB setting, try a linear scale */
snd_mixer_selem_get_playback_volume_range(e, &min, &max);
set = min + volume * (max - min) / 100;
snd_mixer_selem_set_playback_volume_all(e, set);
}
}
}
}
static espeak_ERROR set_volume(struct synth_t *s, int vol, enum adjust_t adj)
{
espeak_ERROR rc;
if (adj == ADJ_DEC)
vol = -vol;
if (adj != ADJ_SET)
vol += s->volume;
rc = espeak_SetParameter(espeakVOLUME, (vol + 1) * volumeMultiplier, 0);
if (rc == EE_OK) {
s->volume = vol;
if (alsaVolume)
set_alsa_volume(vol);
}
return rc;
}
static espeak_ERROR stop_speech(void)
{
espeak_ERROR rc;
rc = espeak_Cancel();
return rc;
}
static espeak_ERROR speak_text(struct synth_t *s)
{
espeak_ERROR rc;
int synth_mode = 0;
if (espeakup_mode == ESPEAKUP_MODE_ACSINT)
synth_mode |= espeakSSML;
if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) {
char *buf = NULL;
int n;
unsigned char c = s->buf[0];
if (c == ' ')
n = asprintf(&buf,
"<say-as interpret-as=\"tts:char\">&#32;</say-as>");
else if (c < 0x20 || c > 0x7e) {
/* Not a printable character; do not embed it in SSML, as
* that would produce invalid markup. Fall through to the
* raw-synthesis path below. */
n = -1;
} else {
/* Escape characters that are special in XML/SSML so the
* resulting markup stays well-formed; otherwise espeak-ng
* misparses the <say-as> element, which is the source of
* the spurious high-pitched "ringing" on these characters. */
const char *entity = NULL;
switch (c) {
case '<': entity = "&lt;"; break;
case '>': entity = "&gt;"; break;
case '&': entity = "&amp;"; break;
case '\'': entity = "&apos;"; break;
case '"': entity = "&quot;"; break;
}
if (entity)
n = asprintf(&buf,
"<say-as interpret-as=\"characters\">%s</say-as>",
entity);
else
n = asprintf(&buf,
"<say-as interpret-as=\"characters\">%c</say-as>",
c);
}
if (n == -1) {
/* D'oh. Not much to do on allocation failure.
* Perhaps espeak will happen to say the character */
rc = espeak_Synth(s->buf, s->len + 1, 0, POS_CHARACTER, 0,
synth_mode, NULL, NULL);
} else {
rc = espeak_Synth(buf, n + 1, 0, POS_CHARACTER, 0, espeakSSML, NULL,
NULL);
free(buf);
}
} else
rc = espeak_Synth(s->buf, s->len + 1, 0, POS_CHARACTER, 0, synth_mode,
NULL, NULL);
return rc;
}
static void free_espeak_entry(struct espeak_entry_t *entry)
{
assert(entry);
if (entry->cmd == CMD_SPEAK_TEXT)
free(entry->buf);
free(entry);
}
static void synth_queue_clear()
{
struct espeak_entry_t *current;
while (queue_peek(synth_queue)) {
current = (struct espeak_entry_t *) queue_remove(synth_queue);
free_espeak_entry(current);
}
}
static int reinitialize_espeak(struct synth_t *s)
{
int rate;
/* Re-initialize espeak */
rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0);
if (rate < 0) {
fprintf(stderr, "Unable to initialize espeak.\n");
return -1;
}
espeak_SetSynthCallback(callback);
/* Set parameters again */
espeak_SetVoiceByName(s->voice);
espeak_SetParameter(espeakRANGE, s->frequency * frequencyMultiplier, 0);
espeak_SetParameter(espeakPITCH, s->pitch * pitchMultiplier, 0);
espeak_SetParameter(espeakRATE, s->rate * rateMultiplier + rateOffset, 0);
espeak_SetParameter(espeakVOLUME, (s->volume + 1) * volumeMultiplier, 0);
espeak_SetParameter(espeakCAPITALS, 0, 0);
paused_espeak = 0;
return 0;
}
/* Wait for up to a second before retrying an entry which could not be
* processed, so that we do not busy-loop on a persistent error. Called
* and returns with queue_guard held. Wakes up immediately if a stop is
* requested. */
static void espeak_wait_retry(void)
{
struct timespec timeout;
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec++;
pthread_cond_timedwait(&wake_stop, &queue_guard, &timeout);
}
/* Handle an entry which could not be processed. Called and returns
* with queue_guard held.
* Normally just back off before the retry, but watch out for a wedged
* engine: if entries keep failing while the synth callback shows no
* progress at all, restart the engine, and if restarting does not help
* either, exit so that the init system respawns us in a clean state. */
static void espeak_handle_failure(struct synth_t *s)
{
if (atomic_exchange(&synth_progressed, 0)) {
/* Espeak is making progress, it is merely backlogged. */
stalled_retries = 0;
} else if (++stalled_retries >= ESPEAK_STALL_RETRIES) {
stalled_retries = 0;
if (++restart_attempts > ESPEAK_MAX_RESTARTS) {
fprintf(stderr, "espeakup: espeak keeps failing without "
"making progress and restarting it did not help, "
"aborting\n");
/* Use _exit because exit could hang in library destructors
* while the audio device is wedged. */
_exit(3);
}
fprintf(stderr, "espeakup: espeak has been failing without "
"making progress for %d seconds, restarting it\n",
ESPEAK_STALL_RETRIES);
/* Call into espeak with queue_guard released: these calls can
* take time, or block on a wedged audio device. If they do
* block forever, the stop-acknowledgement timeout in the
* softsynth thread is our last resort. */
pthread_mutex_unlock(&queue_guard);
if (!paused_espeak) {
espeak_Cancel();
espeak_Terminate();
paused_espeak = 1;
}
reinitialize_espeak(s);
clock_gettime(CLOCK_MONOTONIC, &last_restart);
pthread_mutex_lock(&queue_guard);
return;
}
espeak_wait_retry();
}
static struct espeak_entry_t *current = NULL;
static void queue_process_entry(struct synth_t *s)
{
espeak_ERROR error = EE_OK;
char markbuff[50];
if (current != queue_peek(synth_queue)) {
if (current)
free_espeak_entry(current);
current = queue_peek(synth_queue);
}
pthread_mutex_unlock(&queue_guard);
if (current->cmd != CMD_PAUSE && paused_espeak) {
if (reinitialize_espeak(s) < 0) {
/* Espeak is unavailable, so the entry cannot be processed.
* Calling espeak functions on a terminated engine would
* just fail (or worse). Leave the entry queued and retry
* after a small pause. */
pthread_mutex_lock(&queue_guard);
espeak_handle_failure(s);
return;
}
}
switch (current->cmd) {
case CMD_SET_FREQUENCY:
error = set_frequency(s, current->value, current->adjust);
break;
case CMD_SET_MARK:
snprintf(markbuff, sizeof(markbuff), "<mark name=\"%d\"/>",
current->value);
error = espeak_Synth(markbuff, strlen(markbuff) + 1, 0, POS_CHARACTER,
0, espeakSSML, NULL, NULL);
break;
case CMD_SET_PITCH:
error = set_pitch(s, current->value, current->adjust);
break;
case CMD_SET_RANGE:
error = set_range(s, current->value, current->adjust);
break;
case CMD_SET_PUNCTUATION:
error = set_punctuation(s, current->value, current->adjust);
break;
case CMD_SET_RATE:
error = set_rate(s, current->value, current->adjust);
break;
case CMD_SET_VOICE:
error = EE_OK;
break;
case CMD_SET_VOLUME:
error = set_volume(s, current->value, current->adjust);
break;
case CMD_SPEAK_TEXT:
s->buf = current->buf;
s->len = current->len;
error = speak_text(s);
break;
case CMD_PAUSE:
if (!paused_espeak) {
error = espeak_Cancel();
if (error == EE_OK)
error = espeak_Terminate();
if (error == EE_OK)
paused_espeak = 1;
} else {
error = EE_OK;
}
break;
default:
/* Uh? */
error = EE_OK;
break;
}
pthread_mutex_lock(&queue_guard);
if (error == EE_OK) {
/* Processed, drop it */
struct espeak_entry_t *unqueued = queue_remove(synth_queue);
assert(unqueued == current);
free_espeak_entry(current);
current = NULL;
stalled_retries = 0;
if (restart_attempts) {
/* Forget about past restarts once the engine has been
* healthy for a while. Entries can spuriously succeed
* right after a restart while the audio output is still
* wedged (espeak's internal queue is empty again), so a
* quick success must not reset the counter. */
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec - last_restart.tv_sec >= ESPEAK_HEALTHY_SECS)
restart_attempts = 0;
}
} else {
if (error != EE_BUFFER_FULL)
fprintf(stderr, "espeak error: %d\n", error);
/* The entry stays queued and will be retried. Give espeak a
* little break before that, whatever the error: previously only
* EE_BUFFER_FULL throttled, and any other persistent error made
* us retry the same entry in a tight loop, burning a whole CPU. */
espeak_handle_failure(s);
}
}
int initialize_espeak(struct synth_t *s)
{
int rate;
/* initialize espeak */
rate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0);
if (rate < 0) {
fprintf(stderr, "Unable to initialize espeak.\n");
return -1;
}
espeak_SetSynthCallback(callback);
/* Setup initial voice parameters */
if (defaultVoice && defaultVoice[0]) {
set_voice(s, defaultVoice);
free(defaultVoice);
defaultVoice = NULL;
}
set_frequency(s, defaultFrequency, ADJ_SET);
set_pitch(s, defaultPitch, ADJ_SET);
set_range(s, defaultRange, ADJ_SET);
set_rate(s, defaultRate, ADJ_SET);
set_volume(s, defaultVolume, ADJ_SET);
espeak_SetParameter(espeakCAPITALS, 0, 0);
paused_espeak = 0;
return 0;
}
/* espeak_thread is the "main" function of our secondary (queue-processing)
* thread.
* First, lock queue_guard, because it needs to be locked when we call
* pthread_cond_wait on the runner_awake condition variable.
* Next, enter an infinite loop.
* The wait call also unlocks queue_guard, so that the other thread can
* manipulate the queue.
* When runner_awake is signaled, the pthread_cond_wait call re-locks
* queue_guard, and the "queue processor" thread has access to the queue.
* While there is an entry in the queue, call queue_process_entry.
* queue_process_entry unlocks queue_guard after removing an item from the
* queue, so that the main thread doesn't have to wait for us to finish
* processing the entry. So re-lock queue_guard after each call to
* queue_process_entry.
*
* The main thread can add items to the queue in exactly two situations:
* 1. We are waiting on runner_awake, or
* 2. We are processing an entry that has just been removed from the queue.
*/
void *espeak_thread(void *arg)
{
struct synth_t *s = (struct synth_t *) arg;
pthread_mutex_lock(&queue_guard);
while (should_run) {
while (should_run && !queue_peek(synth_queue) && !stop_requested)
pthread_cond_wait(&runner_awake, &queue_guard);
if (stop_requested) {
current = NULL;
/* Call into espeak with queue_guard released: espeak_Cancel
* can take time, or even block indefinitely when the audio
* output is wedged, and holding the lock here would prevent
* the other threads from ever making progress again. The
* queue cannot change concurrently: the only producer (the
* softsynth thread) is blocked waiting for stop_acknowledged
* as long as stop_requested is set. */
pthread_mutex_unlock(&queue_guard);
stop_speech();
pthread_mutex_lock(&queue_guard);
synth_queue_clear();
stop_requested = 0;
pthread_cond_signal(&stop_acknowledged);
}
while (should_run && queue_peek(synth_queue) && !stop_requested) {
queue_process_entry(s);
}
}
pthread_cond_signal(&stop_acknowledged);
pthread_mutex_unlock(&queue_guard);
return NULL;
}

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
@ -18,17 +18,18 @@
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/file.h>
#include <time.h>
#include <unistd.h>
#include "espeakup.h"
/* path to our pid file */
// path to our pid file
char *pidPath = "/var/run/espeakup.pid";
int debug = 0;
@ -40,7 +41,9 @@ volatile int should_run = 1;
espeak_AUDIO_OUTPUT audio_mode;
pthread_cond_t runner_awake = PTHREAD_COND_INITIALIZER;
pthread_cond_t stop_acknowledged = PTHREAD_COND_INITIALIZER;
/* Initialized in main: use the monotonic clock for timed waits. */
pthread_cond_t wake_stop;
pthread_cond_t stop_acknowledged;
pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER;
int espeakup_start_daemon(void)
@ -60,7 +63,7 @@ int espeakup_start_daemon(void)
exit(1);
}
if (pid) {
/* Parent, just wait for daemon */
// Parent, just wait for daemon
if (read(fds[0], &c, 1) < 0) {
printf("Espeakup is already running!\n");
exit(1);
@ -68,17 +71,17 @@ int espeakup_start_daemon(void)
exit(c);
}
/* Child, create new session */
// Child, create new session
setsid();
pid = fork();
if (pid)
/* Intermediate child, just exit */
// Intermediate child, just exit
exit(0);
/* Child */
// Child
if (chdir("/") < 0) {
c = 1;
(void)write(fds[1], &c, 1);
(void) write(fds[1], &c, 1);
exit(1);
}
return fds[1];
@ -99,20 +102,18 @@ int espeakup_is_running(void)
}
if (flock(pidFile, LOCK_EX) < 0) {
printf("Can not lock the pid file %s: %s\n", pidPath,
strerror(errno));
printf("Can not lock the pid file %s: %s\n", pidPath, strerror(errno));
goto error;
}
n = read(pidFile, s, sizeof(s) - 1);
if (n < 0) {
printf("Can not read the pid file %s: %s\n", pidPath,
strerror(errno));
printf("Can not read the pid file %s: %s\n", pidPath, strerror(errno));
goto error;
}
s[n] = 0;
n = sscanf(s, "%d", &pid);
if (n == 1 && (!kill(pid, 0) || errno != ESRCH)) {
/* Already running */
// Already running
close(pidFile);
return 1;
}
@ -148,6 +149,18 @@ int main(int argc, char **argv)
struct synth_t s = {
.voice = "",
};
pthread_condattr_t monotonic_attr;
/* Condition variables used with pthread_cond_timedwait must use the
* monotonic clock, so that wall-clock adjustments (NTP, an
* installer setting the system time) cannot make the timeouts fire
* too early or far too late. */
pthread_condattr_init(&monotonic_attr);
pthread_condattr_setclock(&monotonic_attr, CLOCK_MONOTONIC);
pthread_cond_init(&wake_stop, &monotonic_attr);
pthread_cond_init(&stop_acknowledged, &monotonic_attr);
pthread_condattr_destroy(&monotonic_attr);
synth_queue = new_queue();
if (!synth_queue) {
@ -155,13 +168,13 @@ int main(int argc, char **argv)
return 2;
}
/* set up the pipe used to wake the espeak thread */
// set up the pipe used to wake the espeak thread
if (pipe(self_pipe_fds) < 0) {
perror("Unable to create pipe");
return 5;
}
/* process command line options */
// process command line options
process_cli(argc, argv);
if (!debug && espeakup_mode == ESPEAKUP_MODE_SPEAKUP) {
@ -181,7 +194,7 @@ int main(int argc, char **argv)
close(devnull);
}
/* create the signal processing thread here. */
// create the signal processing thread here.
err = pthread_create(&signal_thread_id, NULL, signal_thread, NULL);
if (err != 0) {
ret = 4;
@ -197,26 +210,26 @@ int main(int argc, char **argv)
sigaddset(&sigset, SIGTERM);
sigprocmask(SIG_BLOCK, &sigset, NULL);
/* Initialize espeak */
// Initialize espeak
if (initialize_espeak(&s) < 0) {
ret = 2;
goto out;
}
/* open the softsynth */
// open the softsynth
if (open_softsynth() < 0) {
ret = 2;
goto out;
}
/* Spawn our softsynth thread. */
// Spawn our softsynth thread.
err = pthread_create(&softsynth_thread_id, NULL, softsynth_thread, &s);
if (err != 0) {
ret = 4;
goto out;
}
/* Spawn our espeak-interacting thread. */
// Spawn our espeak-interacting thread.
err = pthread_create(&espeak_thread_id, NULL, espeak_thread, &s);
if (err != 0) {
ret = 4;
@ -224,14 +237,15 @@ int main(int argc, char **argv)
}
if (!debug && espeakup_mode == ESPEAKUP_MODE_SPEAKUP)
(void)write(fd, &ret, 1);
(void) write(fd, &ret, 1);
/* wait for the threads to shut down. */
// wait for the threads to shut down.
pthread_join(signal_thread_id, NULL);
pthread_join(softsynth_thread_id, NULL);
pthread_join(espeak_thread_id, NULL);
espeak_Terminate();
if (!paused_espeak)
espeak_Terminate();
close_softsynth();
out:
@ -239,9 +253,8 @@ out:
if (ret != 1)
unlink(pidPath);
if (ret != 0)
(void)write(fd, &ret, 1);
/* If ret was 0, the status byte was written before joining
* the threads. */
(void) write(fd, &ret, 1);
// If ret was 0, the status byte was written before joining the threads.
}
return ret;
}

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
@ -20,35 +20,40 @@
#ifndef __ESPEAKUP_H
#define __ESPEAKUP_H
/* This was added for gcc 4.3 */
#include <stddef.h>
// This was added for gcc 4.3
#include <pthread.h>
#include <stddef.h>
#include <espeak/speak_lib.h>
#include <espeak-ng/speak_lib.h>
#include "queue.h"
#define PACKAGE_VERSION "0.80"
#define PACKAGE_BUGREPORT "http://github.com/williamh/espeakup/issues"
#define PACKAGE_BUGREPORT "https://github.com/linux-speakup/espeakup/issues"
enum espeakup_mode_t {
enum espeakup_mode_t
{
ESPEAKUP_MODE_SPEAKUP,
ESPEAKUP_MODE_ACSINT
};
enum command_t {
enum command_t
{
CMD_SET_FREQUENCY,
CMD_SET_MARK,
CMD_SET_PITCH,
CMD_SET_RANGE,
CMD_SET_PUNCTUATION,
CMD_SET_RATE,
CMD_SET_VOICE,
CMD_SET_VOLUME,
CMD_SPEAK_TEXT,
CMD_FLUSH,
CMD_PAUSE,
CMD_UNKNOWN,
};
enum adjust_t {
enum adjust_t
{
ADJ_DEC,
ADJ_SET,
ADJ_INC,
@ -65,9 +70,10 @@ struct espeak_entry_t {
struct synth_t {
int frequency;
int pitch;
int range;
int punct;
int rate;
char voice[10];
char voice[20];
int volume;
char *buf;
int len;
@ -84,13 +90,16 @@ extern void *espeak_thread(void *arg);
extern int open_softsynth(void);
extern void close_softsynth(void);
extern void *softsynth_thread(void *arg);
extern void softsynth_reportindex(int index);
extern volatile int should_run;
extern volatile int stop_requested;
extern int paused_espeak;
extern int self_pipe_fds[2];
#define PIPE_READ_FD (self_pipe_fds[0])
#define PIPE_WRITE_FD (self_pipe_fds[1])
extern pthread_cond_t runner_awake;
extern pthread_cond_t wake_stop;
extern pthread_cond_t stop_acknowledged;
extern pthread_mutex_t queue_guard;

10
src/meson.build Normal file
View file

@ -0,0 +1,10 @@
espeakup_sources = files([
'cli.c',
'espeak.c',
'espeakup.c',
'queue.c',
'signal.c',
'softsynth.c',
'stringhandling.c'
])
espeakup_version = vcs_tag(input : 'version.h.in', output : 'version.h')

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Note that these functions are meant to be used in either a single or
* multi-threaded environment, so they know nothing about mutexes, etc.

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
@ -20,7 +20,7 @@
#ifndef __QUEUE_H
#define __QUEUE_H
struct queue_t; /* An opaque type. */
struct queue_t; // An opaque type.
extern struct queue_t *new_queue(void);
extern int queue_add(struct queue_t *q, void *entry);

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
@ -21,10 +21,11 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define STOP_MSG "s"
#include "espeakup.h"
#define STOP_MSG "s"
/*
* We install a dummy signal handler to let the o/s know that we
* do not want the default action to be performed since we are
@ -40,8 +41,8 @@ void *signal_thread(void *arg)
sigset_t sigset;
int sig;
memset(&temp, 0, sizeof (struct sigaction));
/* install dummy handlers for the signals we want to process */
memset(&temp, 0, sizeof(struct sigaction));
// install dummy handlers for the signals we want to process
temp.sa_handler = dummy_handler;
sigemptyset(&temp.sa_mask);
sigaction(SIGINT, &temp, NULL);
@ -57,14 +58,24 @@ void *signal_thread(void *arg)
case SIGTERM:
pthread_mutex_lock(&queue_guard);
should_run = 0;
/* Wake up any thread waiting on a condition variable so
* that it notices the shutdown request: the softsynth
* thread may be waiting for a stop acknowledgement, and
* the espeak thread may be waiting for work or throttling
* before a retry. */
pthread_cond_broadcast(&runner_awake);
pthread_cond_broadcast(&wake_stop);
pthread_cond_broadcast(&stop_acknowledged);
pthread_mutex_unlock(&queue_guard);
break;
default:
printf("espeakup caught signal %d\n", sig);
break;
}
pthread_mutex_lock(&queue_guard);
}
/* Tell the reader to stop, if it is in a select() call. */
pthread_mutex_unlock(&queue_guard);
// Tell the reader to stop, if it is in a select() call.
write(PIPE_WRITE_FD, STOP_MSG, strlen(STOP_MSG));
return NULL;
}

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
@ -17,27 +17,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <string.h>
#include <ctype.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include "espeakup.h"
#include "stringhandling.h"
/* max buffer size */
// max buffer size
static const size_t maxBufferSize = 16 * 1024 + 1;
/* synth flush character */
// synth flush character
static const int synthFlushChar = 0x18;
static int softFD = 0;
/* Text accumulator: */
// Text accumulator:
char *textAccumulator;
int textAccumulator_l;
@ -124,15 +125,24 @@ static int process_command(struct synth_t *s, char *buf, int start)
case 'f':
cmd = CMD_SET_FREQUENCY;
break;
case 'i':
cmd = CMD_SET_MARK;
break;
case 'p':
cmd = CMD_SET_PITCH;
break;
case 'r':
cmd = CMD_SET_RANGE;
break;
case 's':
cmd = CMD_SET_RATE;
break;
case 'v':
cmd = CMD_SET_VOLUME;
break;
case 'P':
cmd = CMD_PAUSE;
break;
default:
cmd = CMD_UNKNOWN;
break;
@ -146,8 +156,7 @@ static int process_command(struct synth_t *s, char *buf, int start)
}
if (cmd != CMD_FLUSH && cmd != CMD_UNKNOWN) {
if (espeakup_mode == ESPEAKUP_MODE_ACSINT
&& textAccumulator_l != 0) {
if (espeakup_mode == ESPEAKUP_MODE_ACSINT && textAccumulator_l != 0) {
queue_add_text(textAccumulator, textAccumulator_l);
free(textAccumulator);
textAccumulator = initString(&textAccumulator_l);
@ -168,7 +177,8 @@ static void process_buffer(struct synth_t *s, char *buf, ssize_t length)
start = 0;
end = 0;
while (start < length) {
while ((buf[end] < 0 || buf[end] >= ' ') && end < length)
while ((buf[end] < 0 || buf[end] >= ' ' || buf[end] == '\n') &&
end < length)
end++;
if (end != start) {
txtLen = end - start;
@ -183,8 +193,7 @@ static void process_buffer(struct synth_t *s, char *buf, ssize_t length)
}
}
static void process_buffer_acsint(struct synth_t *s, char *buf,
ssize_t length)
static void process_buffer_acsint(struct synth_t *s, char *buf, ssize_t length)
{
int start = 0;
int i;
@ -198,8 +207,8 @@ static void process_buffer_acsint(struct synth_t *s, char *buf,
break;
}
if (i > start)
stringAndBytes(&textAccumulator, &textAccumulator_l,
buf + start, i - start);
stringAndBytes(&textAccumulator, &textAccumulator_l, buf + start,
i - start);
if (flushIt) {
if (textAccumulator != EMPTYSTRING) {
queue_add_text(textAccumulator, textAccumulator_l);
@ -215,29 +224,56 @@ static void process_buffer_acsint(struct synth_t *s, char *buf,
}
}
/* How long to wait for the espeak thread to acknowledge a stop request
* before concluding that espeak is wedged beyond in-process recovery. */
static const int stopAckTimeout = 10;
static void request_espeak_stop(void)
{
struct timespec timeout;
int err = 0;
pthread_mutex_lock(&queue_guard);
stop_requested = 1;
pthread_cond_signal(&runner_awake); /* Wake runner, if necessary. */
while (should_run && stop_requested)
pthread_cond_wait(&stop_acknowledged, &queue_guard); /* wait for acknowledgement. */
pthread_cond_signal(&runner_awake); // Wake runner, if necessary.
pthread_cond_signal(&wake_stop); // Wake runner, if necessary.
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec += stopAckTimeout;
while (should_run && stop_requested && err != ETIMEDOUT)
// wait for acknowledgement.
err = pthread_cond_timedwait(&stop_acknowledged, &queue_guard,
&timeout);
if (should_run && stop_requested) {
/* The espeak thread is stuck in a call into espeak, most likely
* on a wedged audio device. There is no way to recover from
* within the process: exit so that the init system can respawn
* us in a clean state, rather than staying silent, ignoring
* SIGTERM, and stalling the whole console by not draining
* /dev/softsynth anymore. Use _exit because exit could hang in
* library destructors while the audio device is wedged. */
fprintf(stderr, "espeakup: espeak did not acknowledge a stop "
"request within %d seconds, aborting\n", stopAckTimeout);
_exit(3);
}
pthread_mutex_unlock(&queue_guard);
}
int open_softsynth(void)
{
int rc = 0;
/* If we're in acsint mode, we read from stdin. No need to open. */
// If we're in acsint mode, we read from stdin. No need to open.
if (espeakup_mode == ESPEAKUP_MODE_ACSINT) {
softFD = STDIN_FILENO;
return 0;
}
/* open the softsynth. */
softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK);
// open the softsynth.
softFD = open("/dev/softsynthu", O_RDWR | O_NONBLOCK);
if (softFD < 0 && errno == ENOENT)
// Kernel without unicode support? Try without unicode.
softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK);
if (softFD < 0) {
perror("Unable to open the softsynth device");
perror("Unable to open the /dev/softsynth device");
rc = -1;
}
return rc;
@ -319,3 +355,16 @@ void *softsynth_thread(void *arg)
pthread_mutex_unlock(&queue_guard);
return NULL;
}
void softsynth_reportindex(int index)
{
if (espeakup_mode == ESPEAKUP_MODE_ACSINT) {
putchar(index);
fflush(stdout);
} else {
char buf[16];
snprintf(buf, sizeof(buf), "%d", index);
if (write(softFD, buf, strlen(buf)) < 0)
perror("Writing index failed");
}
}

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2011 William Hubbs
*
@ -62,6 +62,17 @@ void *reallocMem(void *p, size_t n)
return s;
}
char *dupeString(char *s)
{
char *c;
if (!(c = strdup(s))) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
return c;
}
char *initString(int *l)
{
*l = 0;
@ -75,9 +86,9 @@ void stringAndString(char **s, int *l, const char *t)
oldlen = *l;
newlen = oldlen + strlen(t);
*l = newlen;
++newlen; /* room for the 0 */
++newlen; // room for the 0
x = oldlen ^ newlen;
if (x > oldlen) { /* must realloc */
if (x > oldlen) { // must realloc
newlen |= (newlen >> 1);
newlen |= (newlen >> 2);
newlen |= (newlen >> 4);
@ -98,7 +109,7 @@ void stringAndBytes(char **s, int *l, const char *t, int cnt)
*l = newlen;
++newlen;
x = oldlen ^ newlen;
if (x > oldlen) { /* must realloc */
if (x > oldlen) { // must realloc
newlen |= (newlen >> 1);
newlen |= (newlen >> 2);
newlen |= (newlen >> 4);

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2011 William Hubbs
*
@ -26,6 +26,7 @@ extern char *EMPTYSTRING;
void *allocMem(size_t n);
void *reallocMem(void *p, size_t n);
char *dupeString(char *s);
char *initString(int *l);
void stringAndString(char **s, int *l, const char *t);
void stringAndBytes(char **s, int *l, const char *t, int cnt);

25
src/version.h.in Normal file
View file

@ -0,0 +1,25 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2008 William Hubbs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __VERSION_H
#define __VERSION_H
#define PACKAGE_VERSION "@VCS_TAG@"
#endif