Compare commits

..

No commits in common. "master" and "v0.80" have entirely different histories.

28 changed files with 2423 additions and 1098 deletions

View file

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

5
.gitignore vendored
View file

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

1806
ChangeLog Normal file

File diff suppressed because it is too large Load diff

45
Makefile Normal file
View file

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

70
README Normal file
View file

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

View file

@ -1,48 +0,0 @@
# 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 Normal file
View file

@ -0,0 +1,11 @@
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.

View file

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

View file

@ -1,77 +0,0 @@
<!-- 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)

View file

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

328
espeak.c Normal file
View file

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

78
espeakup.8 Normal file
View file

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

View file

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

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
* espeakup - interface which allows speakup to use espeak
*
* Copyright (C) 2008 William Hubbs
*
@ -20,40 +20,35 @@
#ifndef __ESPEAKUP_H
#define __ESPEAKUP_H
// This was added for gcc 4.3
#include <pthread.h>
/* This was added for gcc 4.3 */
#include <stddef.h>
#include <pthread.h>
#include <espeak-ng/speak_lib.h>
#include <espeak/speak_lib.h>
#include "queue.h"
#define PACKAGE_BUGREPORT "https://github.com/linux-speakup/espeakup/issues"
#define PACKAGE_VERSION "0.80"
#define PACKAGE_BUGREPORT "http://github.com/williamh/espeakup/issues"
enum espeakup_mode_t
{
enum espeakup_mode_t {
ESPEAKUP_MODE_SPEAKUP,
ESPEAKUP_MODE_ACSINT
};
enum command_t
{
enum command_t {
CMD_SET_FREQUENCY,
CMD_SET_MARK,
CMD_SET_PITCH,
CMD_SET_RANGE,
CMD_SET_PUNCTUATION,
CMD_SET_RATE,
CMD_SET_VOICE,
CMD_SET_VOLUME,
CMD_SPEAK_TEXT,
CMD_FLUSH,
CMD_PAUSE,
CMD_UNKNOWN,
};
enum adjust_t
{
enum adjust_t {
ADJ_DEC,
ADJ_SET,
ADJ_INC,
@ -70,10 +65,9 @@ struct espeak_entry_t {
struct synth_t {
int frequency;
int pitch;
int range;
int punct;
int rate;
char voice[20];
char voice[10];
int volume;
char *buf;
int len;
@ -90,16 +84,13 @@ 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;

View file

@ -1,25 +0,0 @@
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)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +0,0 @@
[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

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

View file

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

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
* espeakup - interface which allows speakup to use espeak
*
* Copyright (C) 2008 William Hubbs
*
@ -17,28 +17,27 @@
* 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 <sys/select.h>
#include <string.h>
#include <ctype.h>
#include "espeakup.h"
#include "stringhandling.h"
// max buffer size
/* max buffer size */
static const size_t maxBufferSize = 16 * 1024 + 1;
// synth flush character
/* synth flush character */
static const int synthFlushChar = 0x18;
static int softFD = 0;
// Text accumulator:
/* Text accumulator: */
char *textAccumulator;
int textAccumulator_l;
@ -125,24 +124,15 @@ static int process_command(struct synth_t *s, char *buf, int start)
case 'f':
cmd = CMD_SET_FREQUENCY;
break;
case 'i':
cmd = CMD_SET_MARK;
break;
case 'p':
cmd = CMD_SET_PITCH;
break;
case 'r':
cmd = CMD_SET_RANGE;
break;
case 's':
cmd = CMD_SET_RATE;
break;
case 'v':
cmd = CMD_SET_VOLUME;
break;
case 'P':
cmd = CMD_PAUSE;
break;
default:
cmd = CMD_UNKNOWN;
break;
@ -156,7 +146,8 @@ static int process_command(struct synth_t *s, char *buf, int start)
}
if (cmd != CMD_FLUSH && cmd != CMD_UNKNOWN) {
if (espeakup_mode == ESPEAKUP_MODE_ACSINT && textAccumulator_l != 0) {
if (espeakup_mode == ESPEAKUP_MODE_ACSINT
&& textAccumulator_l != 0) {
queue_add_text(textAccumulator, textAccumulator_l);
free(textAccumulator);
textAccumulator = initString(&textAccumulator_l);
@ -177,8 +168,7 @@ static void process_buffer(struct synth_t *s, char *buf, ssize_t length)
start = 0;
end = 0;
while (start < length) {
while ((buf[end] < 0 || buf[end] >= ' ' || buf[end] == '\n') &&
end < length)
while ((buf[end] < 0 || buf[end] >= ' ') && end < length)
end++;
if (end != start) {
txtLen = end - start;
@ -193,7 +183,8 @@ static void process_buffer(struct synth_t *s, char *buf, ssize_t length)
}
}
static void process_buffer_acsint(struct synth_t *s, char *buf, ssize_t length)
static void process_buffer_acsint(struct synth_t *s, char *buf,
ssize_t length)
{
int start = 0;
int i;
@ -207,8 +198,8 @@ static void process_buffer_acsint(struct synth_t *s, char *buf, ssize_t length)
break;
}
if (i > start)
stringAndBytes(&textAccumulator, &textAccumulator_l, buf + start,
i - start);
stringAndBytes(&textAccumulator, &textAccumulator_l,
buf + start, i - start);
if (flushIt) {
if (textAccumulator != EMPTYSTRING) {
queue_add_text(textAccumulator, textAccumulator_l);
@ -224,56 +215,29 @@ static void process_buffer_acsint(struct synth_t *s, char *buf, ssize_t 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_cond_signal(&runner_awake); /* Wake runner, if necessary. */
while (should_run && stop_requested)
pthread_cond_wait(&stop_acknowledged, &queue_guard); /* wait for acknowledgement. */
pthread_mutex_unlock(&queue_guard);
}
int open_softsynth(void)
{
int rc = 0;
// If we're in acsint mode, we read from stdin. No need to open.
/* If we're in acsint mode, we read from stdin. No need to open. */
if (espeakup_mode == ESPEAKUP_MODE_ACSINT) {
softFD = STDIN_FILENO;
return 0;
}
// open the softsynth.
softFD = open("/dev/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);
/* open the softsynth. */
softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK);
if (softFD < 0) {
perror("Unable to open the /dev/softsynth device");
perror("Unable to open the softsynth device");
rc = -1;
}
return rc;
@ -355,16 +319,3 @@ void *softsynth_thread(void *arg)
pthread_mutex_unlock(&queue_guard);
return NULL;
}
void softsynth_reportindex(int index)
{
if (espeakup_mode == ESPEAKUP_MODE_ACSINT) {
putchar(index);
fflush(stdout);
} else {
char buf[16];
snprintf(buf, sizeof(buf), "%d", index);
if (write(softFD, buf, strlen(buf)) < 0)
perror("Writing index failed");
}
}

View file

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

View file

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

View file

@ -1,25 +0,0 @@
/*
* 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

View file

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

View file

@ -1,5 +1,5 @@
/*
* espeakup - interface which allows speakup to use espeak-ng
* espeakup - interface which allows speakup to use espeak
*
* Copyright (C) 2011 William Hubbs
*
@ -26,7 +26,6 @@ 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);