Compare commits

...

197 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
William Hubbs
e55f16b7fd Update ChangeLog 2016-07-25 10:25:09 -05:00
William Hubbs
2964310b24 makefile: add target to generate changelog 2016-07-25 10:22:31 -05:00
William Hubbs
918e8853cd version 0.80 2016-07-24 21:38:56 -05:00
William Hubbs
3e5815429b Add my email address to the copyright statement 2016-07-24 21:38:44 -05:00
Christopher Brannon
8b49d9d211 Fix implicit function declaration warning.
This fixes #7
2016-07-24 15:34:01 -05:00
Christopher Brannon
97adab70de Replace usage of daemon(3).
Original patch and commit message courtesy of:
Samuel Thibault <samuel.thibault@ens-lyon.org>

currently espeakup uses daemon() to do the daemonizing stuff.
Unfortunately, daemon() does things not very appropriately, and there
is notably a delay between the parent exit()ing and the child writing
the pid file. The attached patch reimplements it properly, espeakup
then notably plays much more nicely with systemd.

Modified by Chris to apply to master.
This fixes #8.
2016-07-24 13:39:01 -05:00
Christopher Brannon
9290325489 Fix spelling keystrokes and char-by-char echo.
Use ssml's interpret-as="characters" setting when the kernel reports
just one character. This allows the use of espeak's internationalized
spelling of letters instead of having to maintain spelling ourselves in speakup.

Original patch courtesy of
Samuel Thibault <samuel.thibault@ens-lyon.org>
and modified to work with the current code by Chris.

This fixes #6.
2016-03-11 09:05:43 -06:00
Samuel Thibault
ee099174d8 Allow a voice to be selected by language name
This allows the -V option on the command line to be a language name.
2016-03-10 08:26:12 -06:00
Samuel Thibault
c1ad891f2e Create pid file when espeakup is really ready
This makes sure that we do not report that we are ready until everything
is initialized.
2016-03-10 08:14:10 -06:00
Christopher Brannon
d977243735 Add a missing #include, so that this can be built with musl.
This closes #5.
2015-08-18 08:45:27 -05:00
William Hubbs
d95ee07775 Revert "add indexing support"
This reverts commit e84e000b3e.
I need to think more about how to implement this.
2011-05-09 22:22:14 -05:00
William Hubbs
e84e000b3e add indexing support 2011-05-09 21:47:56 -05:00
William Hubbs
3fbbdf1922 Do not try to remove the pid file unless we are in speakup mode 2011-05-07 10:52:21 -05:00
William Hubbs
58ed438f00 rework two if statements
These if statements were executing code if we were not in acsint mode.
They have been combined and the code is now executed when we are in
speakup mode, which is what we want.
2011-05-07 10:35:57 -05:00
William Hubbs
b2bd1d33a8 make espeakup's default rate closer to espeak's default 2011-05-06 23:43:39 -05:00
William Hubbs
3b4b6d0cbc change code to use allocMem wrapper for memory allocation
One of the new string handling routines is a wrapper for allocating
memory. This commit changes the rest of the code to take advantage of
that wrapper.
2011-05-06 23:08:33 -05:00
Christopher Brannon
6180ff6e49 Don't check to see if espeakup is running in acsint mode.
This check is important when running with speakup, since there can only
be one instance accessing /dev/softsynth.
It is unnecessary in acsint mode.
2011-05-06 14:28:07 +00:00
William Hubbs
ba316a4cd4 separate string handling routines into their own module 2011-05-05 17:31:42 -05:00
Christopher Brannon
c06f18c454 support adapters using the acsint module 2011-05-05 17:29:25 -05:00
William Hubbs
70f74657c2 fix Makefile to use MANMODE to install man pages 2011-05-05 14:52:05 -05:00
William Hubbs
999e6551b5 add pid path option to help 2011-05-05 14:50:23 -05:00
William Hubbs
3353241a79 add command line option to change the pid path
This adds a -P or --pid-path option to the command line which
allows the user to change the path and the name of the pid file created
when espeakup is running as a daemon.

I would like to thank Chris Brannon for the original idea for this.
2011-05-05 14:12:40 -05:00
William Hubbs
49dcacb2ec adjust rate offset and multiplier for espeak 1.45.04 2011-05-05 12:30:18 -05:00
William Hubbs
2154d1a231 use memset to initialize sigaction structure 2011-03-06 15:12:40 -06:00
Christopher Brannon
1990e8e25d Properly initialize sigaction struct.
The sigaction struct used in signal_thread was stored in an automatic
variable.  The fields which were not set manually had undefined values.
2011-03-06 14:42:18 -06:00
William Hubbs
701074fd96 go back to just using a makefile
The reason I went to autotools was the multiple sound systems, but since
we are now just using espeak's audio processing we can go back to a more
simple build system.
2011-03-06 13:09:42 -06:00
William Hubbs
7bf2eee07a remove experimental alsa support
The direct alsa support was experimental and never worked well. It had a
setting which was system specific. Also, I feel that it is better to let
espeak control the audio processing.
2011-03-05 16:49:58 -06:00
William Hubbs
c7ae47dfe5 add experimental support for building a static binary
This is done by adding a --enable-standalone switch to the configure
script.
2010-06-02 12:11:05 -05:00
William Hubbs
3bfc662bae update location of latest version and git repository 2010-05-05 15:11:02 -05:00
William Hubbs
037e642217 rename todo file 2010-05-04 23:31:34 -05:00
William Hubbs
0d9d7b6141 update readme 2010-05-04 23:17:53 -05:00
William Hubbs
056dcf70fe convert to autotools 2010-05-04 22:55:13 -05:00
William Hubbs
d1630432ba re-organized the makefile. 2009-10-10 12:04:41 -05:00
William Hubbs
8fda956e02 fixed permissions in makefile
It turns out that the install commands need to have the permission
options otherwise the permission of everything that is installed is 755,
which is not correct.
2009-10-09 11:20:17 -05:00
William Hubbs
4cfcd7ba11 fixed mandir
the mandir variable in the makefile should point only to the top level
of the man tree.
2009-10-09 11:10:49 -05:00
William Hubbs
1a10788c5f lowered latency setting to 1/40 of a second. 2009-10-09 11:09:20 -05:00
William Hubbs
edb5e50fcd make alsa code more readable
This changes the code to use constants for the parameters to
snd_pcm_set_params.  This makes it easier to read the code and to update
the values if needed.
2009-10-09 08:56:58 -05:00
William Hubbs
dec561324d updates to alsa support
After studying pcm_min.c in the alsa library git repository, I updated
the alsa support to be similar to what I saw there.
2009-10-08 17:46:39 -05:00
William Hubbs
486fe27f47 removed permission settings from makefile 2009-10-07 15:59:12 -05:00
William Hubbs
311b691195 fix makefile to not define variables if they are already defined 2009-10-07 15:55:46 -05:00
William Hubbs
ffe397fb91 removed the minimum function
This function really wasn't needed.  I also attempted to make the
callback functionn more like the test code in the alsa library git
repository.
2009-10-01 14:15:43 -05:00
William Hubbs
e66311bb99 added automatic dependency tracking to the Makefile 2009-09-23 15:44:51 -05:00
William Hubbs
11cc053d82 reworked the makefile
This version of the makefile should be more compatible with allowing
users to pass in cflags.
2009-09-07 16:57:00 -05:00
William Hubbs
48fa03faf5 renamed espeak_sound.c to portaudio.c
This better describes the sound system that espeak uses natively.
2009-09-05 11:32:22 -05:00
William Hubbs
654fc810fe default prefix to /usr/local
Without packaging, we should be installing espeakup in /usr/local.
2009-08-25 18:22:40 -05:00
William Hubbs
e4e3f0979e the status handle should be static 2009-08-21 13:03:02 -05:00
William Hubbs
57547e8efa renamed synth.c to espeak.c
The name was changed because it describes the function of this code more
accurately.
2009-08-19 15:14:20 -05:00
William Hubbs
ac9e12414b made stop_requested a global variable
The two variables, stop_requested and runner_must_stop were performing
the same function, so I am using one variable, stop_requested for this
function.
2009-07-01 20:16:29 -05:00
William Hubbs
18ebab3247 indentation fixes 2009-07-01 19:04:01 -05:00
William Hubbs
cc7e77eb9d move stop_audio call to synth thread
Since there is no reason currently for the softsynth thread to do this,
it makes better sense to have the synth thread control all interaction
with espeak.
2009-07-01 13:01:41 -05:00
William Hubbs
32d848cb76 make callback honor should_run
The callback should return and abort synthesis if should_run is 0.  This
fixes slow shutdown times in alsa mode.
2009-07-01 12:22:14 -05:00
William Hubbs
4de82bf24c added a couple of #defines to the alsa code 2009-06-30 21:32:52 -05:00
William Hubbs
739e074072 reworked the queue_remove function
Now, when queue_remove is called, it returns the pointer to the data of
the first entry in the queue and removes the entry.
2009-06-30 20:15:20 -05:00
William Hubbs
014d27b7aa removed some unlock_audio_mutex() calls 2009-06-30 19:15:44 -05:00
William Hubbs
82490a98d2 call snd_pcm_prepare after snd_pcm_drop in stop_audio 2009-06-30 15:54:52 -05:00
William Hubbs
101e14901e removed white space in the makefile 2009-06-30 13:21:52 -05:00
William Hubbs
dc056e6c77 broke the queue definitions out into their own header file 2009-06-30 13:08:07 -05:00
William Hubbs
0a9a8cce9b small style changes 2009-06-30 08:15:27 -05:00
Christopher Brannon
40479540b4 Fix a memory leak.
If we fail to add entries to the queue in queue_add_cmd or
queue_add_text, properly free the entry.
2009-06-29 18:42:25 -05:00
William Hubbs
ccb8cea91e stop speech before clearing the queue
Thanks to Kirk Reiser for pointing out that this makes the cancel
response faster.
2009-06-29 18:12:04 -05:00
Christopher Brannon
31ad5f04ca Completely data-agnostic queue functions.
The functions in queue.c no longer use static variables.  We can now use
them for multiple queues, if necessary.
2009-06-29 18:07:18 -05:00
Christopher Brannon
011ec27162 Create pipe before starting the signal handler thread.
The thread can write to the pipe, so the pipe must be initialized
before the thread starts.
2009-06-29 17:49:49 -05:00
William Hubbs
04d10d88ec more sound updates
removed the user_data processing code and put the call to snd_pcm_drop
in stop_audio.
2009-06-29 17:48:16 -05:00
William Hubbs
ceaae3640a audio should be stopped in softsynth_thread not espeak_thread 2009-06-29 15:59:55 -05:00
William Hubbs
c9f871f687 see if we need to silence speech before we process the queue 2009-06-29 15:26:48 -05:00
William Hubbs
a82bbd8140 fixed a memory leak
If we processed an entry from the queue successfully, we were removing
the entry itself from the queue but not freeing the memory allocated to
the entry.
2009-06-28 17:56:21 -05:00
William Hubbs
58f09983f8 fixed callback return code
The callback should use the value of stop_requested as its return code.
2009-06-28 15:52:59 -05:00
William Hubbs
0238baa5c2 more alsa updates
Made an 'if' statement in the callback more clear and added some locking
for the audio mutex.
2009-06-28 13:52:31 -05:00
William Hubbs
bcd64cb263 created a start_audio function
This moves audio control to the specific files, espeak_sound.c and
alsa.c, which are tied to the sound systems.
2009-06-27 17:49:57 -05:00
William Hubbs
9b7dcebb6d alsa updates
The first while loop in the callback doesn't need to be a loop.  If the
audio fails, we can just print an error and return.
2009-06-27 17:10:10 -05:00
William Hubbs
46bf3d99d5 all access of the audio mutex should go through our functions 2009-06-27 16:05:27 -05:00
William Hubbs
87ab6c6ea8 make sure that snd_pcm_drop is successful.
This was suggested by Kirk Reiser and Chris Brannon.
2009-06-27 13:32:43 -05:00
William Hubbs
4889572ad1 use user_data to detect old events
When espeak_Cancel is called, change the value of user_data that is
passed to the events, and, in the callback, use this to test to see if
cancel was received.  If the value of user_data has changed, discarde
events that have the old value.  This patch is from Chris Brannon.
2009-06-27 13:20:43 -05:00
William Hubbs
a9398bdeb4 Added another error check for alsa 2009-06-26 23:11:15 -05:00
William Hubbs
be879d206b alsa update
I changed the name of the callback to alsa_callback and removed a line
that was making the amount of data written to the sound card very small.
2009-06-26 16:12:31 -05:00
William Hubbs
c1d33d6738 Set the espeak audio buffer size to 50 ms
This should help make the cancel command more responsive.
2009-06-26 16:00:08 -05:00
William Hubbs
0471ff47f4 add support for the user_data parameter to espeak_synth
The user_data parameter is just a pointer that is passed into the
espeak_synth call that is passed back to the callback.  In native mode,
we are not using it since there is not a callback.  However, in alsa
mode, it will be used to indicate when a cancel was processed.
2009-06-26 11:34:28 -05:00
William Hubbs
520bbae365 renamed stopped to stop_requested
This is more descriptive of what the variable actually does.  It signals
the callback to stop the audio.
2009-06-26 10:03:11 -05:00
William Hubbs
8f3e8f1967 moved the audio_mutex code to alsa
This is not needed for native sound support, so it has been moved into
the alsa specific code.
2009-06-26 09:50:11 -05:00
William Hubbs
e49acbb59a removed a debug print 2009-06-25 22:03:57 -05:00
William Hubbs
24bcdf5666 another termination fix
espeak_thread needs to signal softsynth_thread once more as it is going
town so that softsynth_thread will see that should_run is now 0 and
terminate.
2009-06-25 22:00:25 -05:00
William Hubbs
5ec3809c59 wake up espeak_thread when softsynth_thread terminates
espeak_thread needs a signal since it might be sleeping and
should_run has changed.  This makes sure it terminates.
2009-06-25 21:17:34 -05:00
William Hubbs
5ebe506026 more mutex fixes
Make sure that should_run is protected by the mutex in the softsynth
thread.
2009-06-25 21:07:24 -05:00
Chris Brannon
d5fd5d69b8 don't wait on a condition variable if should_run is false 2009-06-25 20:39:35 -05:00
William Hubbs
633743117c mutex fixes
We need to make sure that should_run is protected by the mutex.
2009-06-25 20:24:34 -05:00
William Hubbs
1750b92cfb fixed signal handling issue
The signal handler stopped working after I moved the initialization
calls to the main function.  Creating the signal handler thread first
fixed this issue.
2009-06-25 20:04:42 -05:00
William Hubbs
eb74a3ce17 removed an unnecessary call to espeak_Terminate() 2009-06-25 18:45:37 -05:00
William Hubbs
dd00775695 initialization update
The main function now initializes espeak and opens the softsynth before
starting the threads.  This insures that the resources we need are
active.
2009-06-25 18:30:36 -05:00
William Hubbs
6a15f2cccf fixed first wait in softsynth thread
The thread should wait if there is nothing in the queue and if there is
not a request to stop.
Thanks to Chris Brannon for the patch.
2009-06-25 18:17:14 -05:00
William Hubbs
3687f16b09 wait for acknowledgements correctly
pthread_cond_wait() can have spurious wakeups, so we need to be sure
that the condition is actually true when we return from this function.
Thanks to Chris Brannon for the patch.
2009-06-25 17:05:51 -05:00
William Hubbs
938e10b66a removed a nested lock/unlock 2009-06-25 14:36:25 -05:00
William Hubbs
975765289b made sure all cond_wait and cond_signal calls are inside lock/unlock
calls
2009-06-25 14:17:21 -05:00
William Hubbs
b8c7247feb removed acknowledge_guard and substituted queue_guard 2009-06-25 14:05:36 -05:00
William Hubbs
1091182f88 removed a debug print call 2009-06-25 12:57:29 -05:00
William Hubbs
0bf2cae5a6 moved lock/unlock in queue_process_entry
The only time queue_process_entry should lock the queue gard is when it
is removing the item from the queue.  This happens only when the item
was successfully processed.
2009-06-25 12:06:51 -05:00
William Hubbs
554a03d26c white space fix 2009-06-25 12:06:33 -05:00
Christopher Brannon
b7f324072f Fix concurrency bugs.
1. Don't lock or unlock queue_guard during queue_clear.
It is locked when queue_clear is called, and it should remain so.
2. Protect runner_must_stop with queue_guard in
the request_espeak_stop function.
The following condition should always hold: queue_guard is locked while
testing or modifying runner_must_stop.
3. Rename stop_guard to acknowledge_guard.  This is a more
descriptive name.  This mutex simply protects the acknowledgement of
the stop request from being lost.
4. Remove the pthread_mutex_lock from the top of queue_process_entry,
because queue_guard is already locked when the function is called.
2009-06-25 11:56:20 -05:00
William Hubbs
a8cad20de8 more multithreading work
Rearranged the queue handling code so that queue.c is generic.  Also
rearranged several functions in the threads.
2009-06-25 09:05:21 -05:00
William Hubbs
42c3f76a08 moved include for pthread.h to espeakup.h 2009-06-25 01:21:44 -05:00
William Hubbs
3e8e7d12ae added back the declaration for softFD 2009-06-24 22:44:18 -05:00
William Hubbs
af5717b8cb moved queue_add_xxx functions to softsynth thread 2009-06-24 22:23:10 -05:00
William Hubbs
c6b57885c9 removed declaration of rate from main 2009-06-24 22:22:34 -05:00
Christopher Brannon
d82bfcd09c Make one thread responsible for handling espeak interaction.
Most of the idea for this change came from William:
Renamed queue_runner to espeak_thread.  Moved espeak initialization
and termination to espeak_thread.  The while loops that process
the queue now use the variable should_run.
2009-06-24 22:11:52 -05:00
William Hubbs
0fdca827b8 check for terminalFD after select()
If terminalFD has something to read, we break out of the loop in the
softsynth thread.
2009-06-24 21:29:46 -05:00
William Hubbs
474580b08b add pipe to wake up the softsynth thread
This adds a pipe to wake up the softsynth thread, in case we receive a
signal while it is in a select.  Thanks to Chris Brannon.
2009-06-24 21:16:30 -05:00
William Hubbs
c5fce64f25 removed open_softsynth and close_softsynth
The thread can now handle the softsynth device, so main doesn't need to
call these functions.
2009-06-24 20:44:42 -05:00
William Hubbs
a58f93cd74 renamed reader_thread to softsynth_thread 2009-06-24 20:19:24 -05:00
William Hubbs
dfc9bbff65 fixed should_run declaration
Removed the local declaration of should_run and set up the extern.
2009-06-24 20:12:15 -05:00
William Hubbs
9d1cabdd0d started work on multi-threading more of the program
The goal is to create threads for the reader, que runner/espeak
processing and signal handling.
As of this commit, this code is still being worked on, so it is broken.
2009-06-24 20:03:31 -05:00
William Hubbs
f58d9984ce Now the queue runner/softsynth handler clears the queue
Thanks to Chris Brannon for the patch.
2009-06-24 16:59:23 -05:00
William Hubbs
24e7d667bc queue fixes
This adds retry processing back to the queue functions.  queue_remove
should only be called after the head entry on the queue is processed
successfully.
2009-06-23 20:34:04 -05:00
William Hubbs
26ab109bd8 moved the check for stop out of the loop
In the callback, we should check to see if the stopped flag is true
whether or not we are processing audio.
2009-06-23 16:13:52 -05:00
William Hubbs
b108764b02 removed the audio_callback variable 2009-06-23 11:02:09 -05:00
Christopher Brannon
739d79cff8 Select audio mode before initializing espeak. 2009-06-23 10:48:56 -05:00
Christopher Brannon
ec9d8b1ee2 Add error-checking to the snd_pcm_set_* calls.
These can fail.  They do more than simply manipulate a structure.
2009-06-22 23:05:07 -05:00
Christopher Brannon
a5d1a48f42 Obtain sample rate from the value of espeak_Initialize.
espeak uses a sample rate of 22050 HZ, but let's not rely on that knowledge.
espeak_Initialize returns the sample rate on success,
so rely on that value when selecting a rate.
2009-06-22 22:32:27 -05:00
William Hubbs
3bb78df86c Do not set the period. 2009-06-22 21:36:31 -05:00
William Hubbs
94e23a3a02 fixed error condition check in alsa.c
The check was looking for a specific error when it should have been just
checking for failure.
2009-06-22 20:44:42 -05:00
William Hubbs
a69342fcad removed some blank lines and put the variables at the top of the file 2009-06-22 19:09:07 -05:00
Christopher Brannon
91b8960b6a Protect the stopped variable with a mutex.
An oversight.  Should have done this in the initial commit.
volatile does not imply atomic.
2009-06-21 14:36:13 -05:00
William Hubbs
0ad70b4eaa Revert "fixed stop_speech issue"
This reverts commit 1c440e5a42.
2009-06-20 19:14:42 -05:00
William Hubbs
1d02169aa5 alsa support is conditional
This commit updates the makefile and the documentation to explain how to
build alsa support.  It has not been fully tested, so it is not built by
default.  Also, I was able to remove the conditional compile directives
from the source.
2009-06-20 16:46:50 -05:00
William Hubbs
1c440e5a42 fixed stop_speech issue
The stop_speech function needs to test the return code from
espeak_Cancel() to be sure the operation was successful before
signaling the callback to stop the audio.
2009-06-20 16:17:42 -05:00
William Hubbs
08e46c58a8 indentation fixes 2009-06-20 13:09:57 -05:00
Christopher Brannon
d373fb2aa7 An initial stab at ALSA support.
It's very raw right now.
2009-06-19 15:11:51 -05:00
William Hubbs
b31985f97c released v0.71 2009-05-30 12:44:33 -05:00
William Hubbs
d7dd0f919d fixed initialization issues
We were not returning exit codes properly if we were unable to open the
softsynth or if the daemon was already running.
2009-05-30 12:37:58 -05:00
William Hubbs
2db53856a9 fixed typo in tarball script 2009-05-30 10:37:39 -05:00
William Hubbs
55f8ebf98a released v0.70 2009-05-30 10:32:36 -05:00
William Hubbs
69f1e8554a The tarball script now adds a ChangeLog 2009-05-30 10:30:45 -05:00
William Hubbs
0e47c95015 updated README 2009-05-30 08:36:33 -05:00
William Hubbs
c235a3b063 added .indent.pro to the repository 2009-05-30 00:20:31 -05:00
William Hubbs
d7c81f5117 indentation fixes 2009-05-29 23:17:28 -05:00
Christopher Brannon
3ddbb94e37 multithreading
Make espeakup a multi-threaded program.  One thread reads from the softsynth
device, queuing text and synthesis commands.  The other thread processes
items from the queue.
2009-05-29 20:00:06 -05:00
William Hubbs
3a8323f98c Fixed typo in README 2009-04-15 08:03:40 -05:00
William Hubbs
e7d300a183 turn off espeak's default processing of uppercase letters
This needs to be turned off since speakup processes upper case by
raising the pitch.
2009-04-14 09:00:32 -05:00
William Hubbs
a1510b5e93 indentation fixes 2009-04-12 16:33:01 -05:00
William Hubbs
3dbdcb21cb Aespeakup should not drop all non-ascii characters.
This fixes an issue with non-english languages.
Thanks to Samuel Thibault for the patch.
2009-04-05 19:24:36 -05:00
34 changed files with 2039 additions and 903 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
...

3
.gitignore vendored
View file

@ -1,2 +1 @@
espeakup
*.o
build*

2
.indent.pro vendored Normal file
View file

@ -0,0 +1,2 @@
-kr
-ts4

View file

@ -1,45 +0,0 @@
INSTALL = install
SRCS = \
cli.c \
espeakup.c \
queue.c \
softsynth.c \
synth.c
OBJS = $(SRCS:.c=.o)
LDLIBS = -lespeak
PREFIX = /usr
MANDIR = $(PREFIX)/share/man/man8
BINDIR = $(PREFIX)/bin
all: espeakup
install: espeakup
$(INSTALL) -d $(DESTDIR)$(BINDIR)
$(INSTALL) -m 0755 $< $(DESTDIR)$(BINDIR)
$(INSTALL) -d $(DESTDIR)$(MANDIR)
$(INSTALL) -m 0644 espeakup.8 $(DESTDIR)$(MANDIR)
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) espeakup
espeakup: $(OBJS)
cli.o: cli.c espeakup.h
espeakup.o: espeakup.c espeakup.h
queue.o: queue.c espeakup.h
softsynth.o: softsynth.c espeakup.h
synth.o: synth.c espeakup.h
%.o: %.c
$(CC) -c -Wall $(CFLAGS) $(CPPFLAGS) -o $@ $<

63
README
View file

@ -1,63 +0,0 @@
espeakup connector
=======================
This is very early alpha software, so please keep that in mind if you
use this program.
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, here are some basic instructions.
To install espeakup, first cd into the directory where you
unpacked the tarball, then issue make to compile the program. Once this
is done, as root, issue make install. This will install espeakup in /usr/bin.
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.
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.
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 Duttington, the
authors of Speakup and Espeak, respectively, for their work.

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

View file

@ -1,72 +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 \-\^\-default-voice=voicename
]
[
.B \-\^\-debug
]
[
.B \-\^\-help
]
[
.B \-\^\-version
]
.SH OPTIONS
.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>.

View file

@ -1,142 +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/>.
*/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "espeakup.h"
/* program version */
const char *Version = "0.60";
/* path to our pid file */
const char *pidPath = "/var/run/espeakup.pid";
/* default voice settings */
const int defaultFrequency = 5;
const int defaultPitch = 5;
const int defaultRate = 5;
const int defaultVolume = 5;
char *defaultVoice = NULL;
int debug = 0;
int espeakup_is_running(void)
{
int rc;
FILE *pidFile;
pid_t pid;
rc = 0;
pidFile = fopen(pidPath, "r");
if (pidFile) {
fscanf(pidFile, "%d", &pid);
fclose(pidFile);
if (!kill(pid, 0) || errno != ESRCH)
rc = 1;
}
return rc;
}
int create_pid_file(void)
{
FILE *pidFile;
pidFile = fopen(pidPath, "w");
if (!pidFile)
return -1;
fprintf(pidFile, "%d\n", getpid());
fclose(pidFile);
return 0;
}
void espeakup_sighandler(int sig)
{
if (debug)
printf("Caught signal %i\n", sig);
/* clear the queue */
queue_clear();
/* shut down espeak and close the softsynth */
espeak_Terminate();
close_softsynth();
if (!debug)
unlink(pidPath);
exit(0);
}
int main(int argc, char **argv)
{
struct synth_t s = {
.voice = "",
};
/* process command line options */
process_cli(argc, argv);
/* Is the espeakup daemon running? */
if (espeakup_is_running()) {
printf("Espeakup is already running!\n");
return 1;
}
if (!debug) {
/* become a daemon */
daemon(0, 1);
/* write our pid file. */
if (create_pid_file() < 0) {
perror("Unable to create pid file");
return 2;
}
}
/* open the softsynth. */
open_softsynth();
/* initialize espeak */
espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0);
/* 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);
/* register signal handler */
signal(SIGINT, espeakup_sighandler);
signal(SIGTERM, espeakup_sighandler);
/* run the main loop */
main_loop(&s);
return 0;
}

View file

@ -1,82 +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/>.
*/
#ifndef __ESPEAKUP_H
#define __ESPEAKUP_H
/* This was added for gcc 4.3 */
#include <stddef.h>
#include <espeak/speak_lib.h>
enum command_t {
CMD_SET_FREQUENCY,
CMD_SET_PITCH,
CMD_SET_PUNCTUATION,
CMD_SET_RATE,
CMD_SET_VOICE,
CMD_SET_VOLUME,
CMD_SPEAK_TEXT,
CMD_FLUSH,
CMD_UNKNOWN,
};
enum adjust_t {
ADJ_DEC,
ADJ_SET,
ADJ_INC,
};
struct synth_t {
int frequency;
int pitch;
int punct;
int rate;
char voice[10];
int volume;
char *buf;
int len;
};
extern int debug;
extern void process_cli(int argc, char **argv);
extern void queue_clear(void);
extern void queue_add_cmd(enum command_t cmd, enum adjust_t adj,
int value);
extern void queue_add_text(char *txt, size_t length);
extern void queue_process_entry(struct synth_t *s);
extern espeak_ERROR set_frequency(struct synth_t *s, int freq,
enum adjust_t adj);
extern espeak_ERROR set_pitch(struct synth_t *s, int pitch,
enum adjust_t adj);
extern espeak_ERROR set_punctuation(struct synth_t *s, int punct,
enum adjust_t adj);
extern espeak_ERROR set_rate(struct synth_t *s, int rate,
enum adjust_t adj);
extern espeak_ERROR set_voice(struct synth_t *s, char *voice);
extern espeak_ERROR set_volume(struct synth_t *s, int vol,
enum adjust_t adj);
extern espeak_ERROR stop_speech(void);
extern espeak_ERROR speak_text(struct synth_t *s);
extern void open_softsynth(void);
extern void close_softsynth(void);
extern void main_loop(struct synth_t *s);
#endif

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')

144
queue.c
View file

@ -1,144 +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/>.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "espeakup.h"
struct queue_entry_t {
enum command_t cmd;
enum adjust_t adjust;
int value;
char *buf;
int len;
struct queue_entry_t *next;
};
static struct queue_entry_t *first = NULL;
static struct queue_entry_t *last = NULL;
static void queue_add(struct queue_entry_t *entry)
{
assert(entry);
entry->next = NULL;
if (!last)
last = entry;
if (!first) {
first = entry;
} else {
first->next = entry;
first = first->next;
}
}
static void queue_remove(void)
{
struct queue_entry_t *temp;
assert(last);
temp = last;
last = temp->next;
if (temp->cmd == CMD_SPEAK_TEXT)
free(temp->buf);
free(temp);
if (!last)
first = last;
}
void queue_clear(void)
{
while (last)
queue_remove();
}
void queue_add_cmd(enum command_t cmd, enum adjust_t adj, int value)
{
struct queue_entry_t *entry;
entry = malloc(sizeof(struct queue_entry_t));
if (!entry) {
perror("unable to allocate memory for queue entry");
return;
}
entry->cmd = cmd;
entry->adjust = adj;
entry->value = value;
queue_add(entry);
}
void queue_add_text(char *txt, size_t length)
{
struct queue_entry_t *entry;
entry = malloc(sizeof(struct queue_entry_t));
if (!entry) {
perror("unable to allocate memory for queue entry");
return;
}
entry->cmd = CMD_SPEAK_TEXT;
entry->adjust = ADJ_SET;
entry->buf = strdup(txt);
if (!entry->buf) {
perror("unable to allocate space for text");
free(entry);
return;
}
entry->len = length;
queue_add(entry);
}
void queue_process_entry(struct synth_t *s)
{
espeak_ERROR error;
if (!last)
return;
switch (last->cmd) {
case CMD_SET_FREQUENCY:
error = set_frequency(s, last->value, last->adjust);
break;
case CMD_SET_PITCH:
error = set_pitch(s, last->value, last->adjust);
break;
case CMD_SET_PUNCTUATION:
error = set_punctuation(s, last->value, last->adjust);
break;
case CMD_SET_RATE:
error = set_rate(s, last->value, last->adjust);
break;
case CMD_SET_VOICE:
break;
case CMD_SET_VOLUME:
error = set_volume(s, last->value, last->adjust);
break;
case CMD_SPEAK_TEXT:
s->buf = last->buf;
s->len = last->len;
error = speak_text(s);
break;
default:
break;
}
if (error == EE_OK)
queue_remove();
}

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,184 +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/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include "espeakup.h"
/* max buffer size */
const size_t maxBufferSize = 1025;
/* synth flush character */
const int synthFlushChar = 0x18;
static int softFD = 0;
static int process_command(struct synth_t *s, char *buf, int start)
{
char *cp;
int value;
enum adjust_t adj;
enum command_t cmd;
cp = buf + start;
switch (*cp) {
case 1:
cp++;
switch (*cp) {
case '+':
adj = ADJ_INC;
cp++;
break;
case '-':
adj = ADJ_DEC;
cp++;
break;
default:
adj = ADJ_SET;
break;
}
value = 0;
while (isdigit(*cp)) {
value = value * 10 + (*cp - '0');
cp++;
}
switch (*cp) {
case 'b':
cmd = CMD_SET_PUNCTUATION;
break;
case 'f':
cmd = CMD_SET_FREQUENCY;
break;
case 'p':
cmd = CMD_SET_PITCH;
break;
case 's':
cmd = CMD_SET_RATE;
break;
case 'v':
cmd = CMD_SET_VOLUME;
break;
default:
cmd = CMD_UNKNOWN;
break;
}
cp++;
break;
default:
cmd = CMD_UNKNOWN;
cp++;
break;
}
if (cmd != CMD_FLUSH && cmd != CMD_UNKNOWN)
queue_add_cmd(cmd, adj, value);
return cp - (buf + start);
}
static void process_buffer(struct synth_t *s, char *buf, ssize_t length)
{
int start;
int end;
char txtBuf[maxBufferSize];
size_t txtLen;
start = 0;
end = 0;
while (start < length) {
while (isprint(buf[end]) && end < length)
end++;
if (end != start) {
txtLen = end - start;
strncpy(txtBuf, buf + start, txtLen);
*(txtBuf + txtLen) = 0;
queue_add_text(txtBuf, txtLen);
}
if (end < length)
start = end = end + process_command(s, buf, end);
else
start = length;
}
}
void open_softsynth(void)
{
softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK);
if (softFD < 0) {
perror("Unable to open the softsynth device");
exit(3);
}
}
void close_softsynth(void)
{
close(softFD);
}
void main_loop(struct synth_t *s)
{
fd_set set;
struct timeval tv;
ssize_t length;
char buf[maxBufferSize];
char *cp;
while (1) {
queue_process_entry(s);
FD_ZERO(&set);
FD_SET(softFD, &set);
tv.tv_sec = 0;
tv.tv_usec = 500;
if (select(softFD + 1, &set, NULL, NULL, &tv) < 0) {
if (errno == EINTR)
continue;
perror("Select failed");
break;
}
if (!FD_ISSET(softFD, &set))
continue;
length = read(softFD, buf, maxBufferSize - 1);
if (length < 0) {
if (errno == EAGAIN || errno == EINTR)
continue;
perror("Read from softsynth failed");
break;
}
*(buf + length) = 0;
cp = strrchr(buf, synthFlushChar);
if (cp) {
queue_clear();
stop_speech();
memmove(buf, cp + 1, strlen(cp + 1) + 1);
length = strlen(buf);
}
process_buffer(s, buf, length);
}
}

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,28 +23,37 @@
#include <string.h>
#include "espeakup.h"
#include "stringhandling.h"
#include "version.h"
/* program version */
extern const char *Version;
/* pid path */
extern char *pidPath;
/* default voice */
extern char *defaultVoice;
/* Whether to drive ALSA volume */
extern int alsaVolume;
/* command line options */
const char *shortOptions = "dhV:v";
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()
{
printf("Usage: espeakup [options]\n\n");
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");
@ -53,10 +62,11 @@ static void show_help()
static void show_version(void)
{
printf("espeakup %s\n", Version);
printf("Copyright (C) 2008 William Hubbs\n");
printf("ESpeakup %s\n", PACKAGE_VERSION);
printf("Copyright (C) 2008 William Hubbs <w.d.hubbs@gmail.com>\n");
printf("License GPLv3+: GNU GPL version 3 or later\n");
printf("You are free to change and redistribute this software.\n");
printf("Please report bugs to %s\n", PACKAGE_BUGREPORT);
exit(0);
}
@ -67,8 +77,14 @@ void process_cli(int argc, char **argv)
do {
opt = getopt_long(argc, argv, shortOptions, longOptions, NULL);
switch (opt) {
case 'P':
pidPath = dupeString(optarg);
break;
case 'V':
defaultVoice = strdup(optarg);
defaultVoice = dupeString(optarg);
break;
case 'a':
espeakup_mode = ESPEAKUP_MODE_ACSINT;
break;
case 'd':
debug = 1;
@ -80,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;
}

260
src/espeakup.c Normal file
View file

@ -0,0 +1,260 @@
/*
* 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/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <time.h>
#include <unistd.h>
#include "espeakup.h"
// path to our pid file
char *pidPath = "/var/run/espeakup.pid";
int debug = 0;
enum espeakup_mode_t espeakup_mode = ESPEAKUP_MODE_SPEAKUP;
struct queue_t *synth_queue = NULL;
int self_pipe_fds[2];
volatile int should_run = 1;
espeak_AUDIO_OUTPUT audio_mode;
pthread_cond_t runner_awake = 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)
{
int fds[2];
pid_t pid;
char c;
if (pipe(fds) < 0) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
// Parent, just wait for daemon
if (read(fds[0], &c, 1) < 0) {
printf("Espeakup is already running!\n");
exit(1);
}
exit(c);
}
// Child, create new session
setsid();
pid = fork();
if (pid)
// Intermediate child, just exit
exit(0);
// Child
if (chdir("/") < 0) {
c = 1;
(void) write(fds[1], &c, 1);
exit(1);
}
return fds[1];
}
int espeakup_is_running(void)
{
int pidFile;
int n;
char s[16];
pid_t pid;
pidFile = open(pidPath, O_RDWR | O_CREAT, 0666);
if (pidFile < 0) {
printf("Can not work with the pid file %s: %s\n", pidPath,
strerror(errno));
return -1;
}
if (flock(pidFile, LOCK_EX) < 0) {
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));
goto error;
}
s[n] = 0;
n = sscanf(s, "%d", &pid);
if (n == 1 && (!kill(pid, 0) || errno != ESRCH)) {
// Already running
close(pidFile);
return 1;
}
if (ftruncate(pidFile, 0) < 0) {
printf("Could not truncate the pid file %s: %s\n", pidPath,
strerror(errno));
goto error;
}
lseek(pidFile, 0, SEEK_SET);
n = snprintf(s, sizeof(s), "%d", getpid());
if (write(pidFile, s, n) < 0) {
printf("Could not write to the pid file %s: %s\n", pidPath,
strerror(errno));
goto error;
}
close(pidFile);
return 0;
error:
close(pidFile);
return -1;
}
int main(int argc, char **argv)
{
int fd, devnull;
char ret = 0;
sigset_t sigset;
int err;
pthread_t signal_thread_id;
pthread_t espeak_thread_id;
pthread_t softsynth_thread_id;
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) {
fprintf(stderr, "Unable to allocate memory.\n");
return 2;
}
// 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_cli(argc, argv);
if (!debug && espeakup_mode == ESPEAKUP_MODE_SPEAKUP) {
fd = espeakup_start_daemon();
if (espeakup_is_running()) {
printf("Espeakup is already running!\n");
ret = 1;
goto out;
}
devnull = open("/dev/null", O_RDWR);
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
if (devnull > 2)
close(devnull);
}
// create the signal processing thread here.
err = pthread_create(&signal_thread_id, NULL, signal_thread, NULL);
if (err != 0) {
ret = 4;
goto out;
}
/*
* Set up the signal mask which will be the default for all threads.
* We are handling sigint and sigterm, so block them.
*/
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
sigprocmask(SIG_BLOCK, &sigset, NULL);
// Initialize espeak
if (initialize_espeak(&s) < 0) {
ret = 2;
goto out;
}
// open the softsynth
if (open_softsynth() < 0) {
ret = 2;
goto out;
}
// 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.
err = pthread_create(&espeak_thread_id, NULL, espeak_thread, &s);
if (err != 0) {
ret = 4;
goto out;
}
if (!debug && espeakup_mode == ESPEAKUP_MODE_SPEAKUP)
(void) write(fd, &ret, 1);
// 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);
if (!paused_espeak)
espeak_Terminate();
close_softsynth();
out:
if (!debug && espeakup_mode == ESPEAKUP_MODE_SPEAKUP) {
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.
}
return ret;
}

106
src/espeakup.h Normal file
View file

@ -0,0 +1,106 @@
/*
* 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 __ESPEAKUP_H
#define __ESPEAKUP_H
// This was added for gcc 4.3
#include <pthread.h>
#include <stddef.h>
#include <espeak-ng/speak_lib.h>
#include "queue.h"
#define PACKAGE_BUGREPORT "https://github.com/linux-speakup/espeakup/issues"
enum espeakup_mode_t
{
ESPEAKUP_MODE_SPEAKUP,
ESPEAKUP_MODE_ACSINT
};
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
{
ADJ_DEC,
ADJ_SET,
ADJ_INC,
};
struct espeak_entry_t {
enum command_t cmd;
enum adjust_t adjust;
int value;
char *buf;
int len;
};
struct synth_t {
int frequency;
int pitch;
int range;
int punct;
int rate;
char voice[20];
int volume;
char *buf;
int len;
};
extern struct queue_t *synth_queue;
extern int debug;
extern enum espeakup_mode_t espeakup_mode;
extern void process_cli(int argc, char **argv);
extern void *signal_thread(void *arg);
extern int initialize_espeak(struct synth_t *s);
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;
#endif

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')

88
src/queue.c Normal file
View file

@ -0,0 +1,88 @@
/*
* 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.
* Handling this is up to the caller.
*
* 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/>.
*/
#include <assert.h>
#include <stdlib.h>
#include "stringhandling.h"
struct queue_entry_t {
void *data;
struct queue_entry_t *next;
};
struct queue_t {
struct queue_entry_t *head;
struct queue_entry_t *tail;
};
struct queue_t *new_queue(void)
{
struct queue_t *q = allocMem(sizeof(struct queue_t));
q->head = NULL;
q->tail = NULL;
return q;
}
int queue_add(struct queue_t *q, void *data)
{
struct queue_entry_t *tmp;
assert(data);
tmp = allocMem(sizeof(struct queue_entry_t));
tmp->data = data;
tmp->next = NULL;
if (!q->tail) {
q->tail = tmp;
} else {
q->tail->next = tmp;
q->tail = q->tail->next;
}
if (!q->head)
q->head = tmp;
return 1;
}
void *queue_remove(struct queue_t *q)
{
void *data = NULL;
struct queue_entry_t *tmp;
if (q->head) {
tmp = q->head;
data = tmp->data;
q->head = tmp->next;
free(tmp);
if (!q->head)
q->tail = q->head;
}
return data;
}
void *queue_peek(struct queue_t *q)
{
if (q->head)
return q->head->data;
else
return NULL;
}

30
src/queue.h Normal file
View file

@ -0,0 +1,30 @@
/*
* 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 __QUEUE_H
#define __QUEUE_H
struct queue_t; // An opaque type.
extern struct queue_t *new_queue(void);
extern int queue_add(struct queue_t *q, void *entry);
extern void *queue_remove(struct queue_t *q);
extern void *queue_peek(struct queue_t *q);
#endif

81
src/signal.c Normal file
View file

@ -0,0 +1,81 @@
/*
* 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/>.
*/
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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
* handling the signal.
*/
static void dummy_handler(int sig)
{
}
void *signal_thread(void *arg)
{
struct sigaction temp;
sigset_t sigset;
int sig;
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);
sigaction(SIGTERM, &temp, NULL);
pthread_mutex_lock(&queue_guard);
while (should_run) {
pthread_mutex_unlock(&queue_guard);
sigfillset(&sigset);
sigwait(&sigset, &sig);
switch (sig) {
case SIGINT:
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);
}
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;
}

370
src/softsynth.c Normal file
View file

@ -0,0 +1,370 @@
/*
* 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/>.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include "espeakup.h"
#include "stringhandling.h"
// max buffer size
static const size_t maxBufferSize = 16 * 1024 + 1;
// synth flush character
static const int synthFlushChar = 0x18;
static int softFD = 0;
// Text accumulator:
char *textAccumulator;
int textAccumulator_l;
static void queue_add_cmd(enum command_t cmd, enum adjust_t adj, int value)
{
struct espeak_entry_t *entry;
int added = 0;
entry = allocMem(sizeof(struct espeak_entry_t));
entry->cmd = cmd;
entry->adjust = adj;
entry->value = value;
pthread_mutex_lock(&queue_guard);
added = queue_add(synth_queue, (void *) entry);
if (!added)
free(entry);
else
pthread_cond_signal(&runner_awake);
pthread_mutex_unlock(&queue_guard);
}
static void queue_add_text(char *txt, size_t length)
{
struct espeak_entry_t *entry;
int added = 0;
entry = allocMem(sizeof(struct espeak_entry_t));
entry->cmd = CMD_SPEAK_TEXT;
entry->adjust = ADJ_SET;
entry->buf = strdup(txt);
if (!entry->buf) {
perror("unable to allocate space for text");
free(entry);
return;
}
entry->len = length;
pthread_mutex_lock(&queue_guard);
added = queue_add(synth_queue, (void *) entry);
if (!added) {
free(entry->buf);
free(entry);
} else {
pthread_cond_signal(&runner_awake);
}
pthread_mutex_unlock(&queue_guard);
}
static int process_command(struct synth_t *s, char *buf, int start)
{
char *cp;
int value;
enum adjust_t adj;
enum command_t cmd;
cp = buf + start;
switch (*cp) {
case 1:
cp++;
switch (*cp) {
case '+':
adj = ADJ_INC;
cp++;
break;
case '-':
adj = ADJ_DEC;
cp++;
break;
default:
adj = ADJ_SET;
break;
}
value = 0;
while (isdigit(*cp)) {
value = value * 10 + (*cp - '0');
cp++;
}
switch (*cp) {
case 'b':
cmd = CMD_SET_PUNCTUATION;
break;
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;
}
cp++;
break;
default:
cmd = CMD_UNKNOWN;
cp++;
break;
}
if (cmd != CMD_FLUSH && cmd != CMD_UNKNOWN) {
if (espeakup_mode == ESPEAKUP_MODE_ACSINT && textAccumulator_l != 0) {
queue_add_text(textAccumulator, textAccumulator_l);
free(textAccumulator);
textAccumulator = initString(&textAccumulator_l);
}
queue_add_cmd(cmd, adj, value);
}
return cp - (buf + start);
}
static void process_buffer(struct synth_t *s, char *buf, ssize_t length)
{
int start;
int end;
char txtBuf[maxBufferSize];
size_t txtLen;
start = 0;
end = 0;
while (start < length) {
while ((buf[end] < 0 || buf[end] >= ' ' || buf[end] == '\n') &&
end < length)
end++;
if (end != start) {
txtLen = end - start;
strncpy(txtBuf, buf + start, txtLen);
*(txtBuf + txtLen) = 0;
queue_add_text(txtBuf, txtLen);
}
if (end < length)
start = end = end + process_command(s, buf, end);
else
start = length;
}
}
static void process_buffer_acsint(struct synth_t *s, char *buf, ssize_t length)
{
int start = 0;
int i;
int flushIt = 0;
while (start < length) {
for (i = start; i < length; i++) {
if (buf[i] == '\r' || buf[i] == '\n')
flushIt = 1;
if (buf[i] >= 0 && buf[i] < ' ')
break;
}
if (i > start)
stringAndBytes(&textAccumulator, &textAccumulator_l, buf + start,
i - start);
if (flushIt) {
if (textAccumulator != EMPTYSTRING) {
queue_add_text(textAccumulator, textAccumulator_l);
free(textAccumulator);
textAccumulator = initString(&textAccumulator_l);
}
flushIt = 0;
}
if (i < length)
start = i = i + process_command(s, buf, i);
else
start = length;
}
}
/* 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.
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 (espeakup_mode == ESPEAKUP_MODE_ACSINT) {
softFD = STDIN_FILENO;
return 0;
}
// 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 /dev/softsynth device");
rc = -1;
}
return rc;
}
void close_softsynth(void)
{
if (softFD)
close(softFD);
}
void *softsynth_thread(void *arg)
{
struct synth_t *s = (struct synth_t *) arg;
fd_set set;
ssize_t length;
char buf[maxBufferSize];
char *cp;
int terminalFD = PIPE_READ_FD;
int greatestFD;
textAccumulator = initString(&textAccumulator_l);
if (terminalFD > softFD)
greatestFD = terminalFD;
else
greatestFD = softFD;
pthread_mutex_lock(&queue_guard);
while (should_run) {
pthread_mutex_unlock(&queue_guard);
FD_ZERO(&set);
FD_SET(softFD, &set);
FD_SET(terminalFD, &set);
if (select(greatestFD + 1, &set, NULL, NULL, NULL) < 0) {
if (errno == EINTR) {
pthread_mutex_lock(&queue_guard);
continue;
}
perror("Select failed");
pthread_mutex_lock(&queue_guard);
break;
}
if (FD_ISSET(terminalFD, &set)) {
pthread_mutex_lock(&queue_guard);
break;
}
if (!FD_ISSET(softFD, &set)) {
pthread_mutex_lock(&queue_guard);
continue;
}
length = read(softFD, buf, maxBufferSize - 1);
if (length < 0) {
if (errno == EAGAIN || errno == EINTR) {
pthread_mutex_lock(&queue_guard);
continue;
}
perror("Read from softsynth failed");
pthread_mutex_lock(&queue_guard);
break;
}
*(buf + length) = 0;
cp = strrchr(buf, synthFlushChar);
if (cp) {
request_espeak_stop();
memmove(buf, cp + 1, strlen(cp + 1) + 1);
length = strlen(buf);
}
if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP)
process_buffer(s, buf, length);
else
process_buffer_acsint(s, buf, length);
pthread_mutex_lock(&queue_guard);
}
pthread_cond_signal(&runner_awake);
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");
}
}

123
src/stringhandling.c Normal file
View file

@ -0,0 +1,123 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2011 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/>.
*/
/*
* These string routines were borrowed from edbrowse, which was
* originally written by Karl Dahlke, and is being currently maintained
* by Christopher Brannon.
* I would like to thank both of them for their work on this software.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *EMPTYSTRING = "";
void *allocMem(size_t n)
{
void *s;
if (!n)
return EMPTYSTRING;
if (!(s = malloc(n))) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
return s;
}
void *reallocMem(void *p, size_t n)
{
void *s;
if (!n) {
fprintf(stderr, "Trying to reallocate memory with size of 0.\n");
exit(1);
}
if (!p) {
fprintf(stderr, "realloc called with a NULL pointer!\n");
exit(1);
}
if (p == EMPTYSTRING)
return allocMem(n);
if (!(s = realloc(p, n))) {
fprintf(stderr, "Failed to allocate memory.\n");
exit(1);
}
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;
return EMPTYSTRING;
}
void stringAndString(char **s, int *l, const char *t)
{
char *p = *s;
int oldlen, newlen, x;
oldlen = *l;
newlen = oldlen + strlen(t);
*l = newlen;
++newlen; // room for the 0
x = oldlen ^ newlen;
if (x > oldlen) { // must realloc
newlen |= (newlen >> 1);
newlen |= (newlen >> 2);
newlen |= (newlen >> 4);
newlen |= (newlen >> 8);
newlen |= (newlen >> 16);
p = reallocMem(p, newlen);
*s = p;
}
strcpy(p + oldlen, t);
}
void stringAndBytes(char **s, int *l, const char *t, int cnt)
{
char *p = *s;
int oldlen, newlen, x;
oldlen = *l;
newlen = oldlen + cnt;
*l = newlen;
++newlen;
x = oldlen ^ newlen;
if (x > oldlen) { // must realloc
newlen |= (newlen >> 1);
newlen |= (newlen >> 2);
newlen |= (newlen >> 4);
newlen |= (newlen >> 8);
newlen |= (newlen >> 16);
p = reallocMem(p, newlen);
*s = p;
}
memcpy(p + oldlen, t, cnt);
p[oldlen + cnt] = 0;
}

34
src/stringhandling.h Normal file
View file

@ -0,0 +1,34 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
*
* Copyright (C) 2011 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 __STRINGHANDLING_H
#define __STRINGHANDLING_H
#include <stddef.h>
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);
#endif

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

126
synth.c
View file

@ -1,126 +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/>.
*/
#include <string.h>
#include "espeakup.h"
/* multipliers and offsets */
const int frequencyMultiplier = 11;
const int pitchMultiplier = 11;
const int rateMultiplier = 34;
const int rateOffset = 84;
const int volumeMultiplier = 22;
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;
}
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;
}
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;
}
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;
}
espeak_ERROR set_voice(struct synth_t * s, char *voice)
{
espeak_ERROR rc;
rc = espeak_SetVoiceByName(voice);
if (rc == EE_OK)
strcpy(s->voice, voice);
return rc;
}
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;
}
espeak_ERROR stop_speech(void)
{
return (espeak_Cancel());
}
espeak_ERROR speak_text(struct synth_t * s)
{
espeak_ERROR rc;
rc = espeak_Synth(s->buf, s->len + 1, 0, POS_CHARACTER, 0, 0, NULL,
NULL);
return rc;
}

18
tarball
View file

@ -1,18 +0,0 @@
#!/bin/bash
# Makes a tarball release
VER=$(./version)
PREFIX=espeakup-${VER}
REL=${1:-v${VER}}
if [ "$REL" != "v${VER}" ]; then
TIMESTAMP=`git show $REL --pretty=format:%ai |head -1`
PATCHLEVEL=`date --utc -d "$TIMESTAMP" +_p%Y%m%d%H%M`
fi
TARFILE=${PREFIX}${PATCHLEVEL}.tar
git archive --format=tar --prefix=${PREFIX}/ $REL > ${TARFILE}
tar f ${TARFILE} --delete ${PREFIX}/.gitignore --delete ${PREFIX}/tarball
bzip2 ${TARFILE}
echo "Produced ${TARFILE}.bz2"

View file

@ -1,5 +0,0 @@
#!/bin/bash
ver=$(grep "const char.*Version" espeakup.c)
ver=${ver%\"*}
ver=${ver#*\"}
echo ${ver}