diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..16c7f08 --- /dev/null +++ b/.clang-format @@ -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 +... diff --git a/.gitignore b/.gitignore index 49299b3..1b2211d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -espeakup -*.o +build* diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 0000000..96ae267 --- /dev/null +++ b/.indent.pro @@ -0,0 +1,2 @@ +-kr +-ts4 diff --git a/Makefile b/Makefile deleted file mode 100644 index fe78cad..0000000 --- a/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -CC = cc -INSTALL = /usr/bin/install -RM = /bin/rm -f - -SRCS = \ - cli.c \ - espeakup.c \ - queue.c \ - softsynth.c \ - synth.c - -OBJS = $(SRCS:.c=.o) - -LDLIBS = -lespeak - -PREFIX = /usr -BINDIR = $(PREFIX)/bin - -ifeq ("$(origin TAG)", "command line") -TIMESTAMP := _p$(shell date +%Y%m%d) -endif - -VERSION := $(shell grep 'Version.*=' cli.c | sed 's/.*"\(.*\)";/\1/') -TAG := v$(VERSION) -TARPREFIX := espeakup-$(VERSION) -TARFILE := $(TARPREFIX)$(TIMESTAMP).tar - -all: espeakup - -install: espeakup - $(INSTALL) -d $(DESTDIR)$(BINDIR) - $(INSTALL) -m 0755 $< $(DESTDIR)$(BINDIR) - -clean: - $(RM) $(OBJS) - -distclean: clean - $(RM) espeakup - -espeakup: $(OBJS) - -tarball: - git archive --format=tar --prefix=$(TARPREFIX)/ $(TAG) > $(TARFILE) - tar f $(TARFILE) --delete $(TARPREFIX)/.gitignore - bzip2 $(TARFILE) - -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 diff --git a/README b/README deleted file mode 100644 index 8d4e284..0000000 --- a/README +++ /dev/null @@ -1,62 +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: - ---debug, -d Debug mode (stay in the foreground) ---help, -h Show 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. - diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1395b0 --- /dev/null +++ b/README.md @@ -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. diff --git a/ToDo b/ToDo deleted file mode 100644 index ec1d210..0000000 --- a/ToDo +++ /dev/null @@ -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. - diff --git a/doc/espeakup.8.ronn b/doc/espeakup.8.ronn new file mode 100644 index 0000000..598611c --- /dev/null +++ b/doc/espeakup.8.ronn @@ -0,0 +1,77 @@ + + +# espeakup(8) --- connect Speakup to the espeak-ng TTS engine + +## SYNOPSIS + +`espeakup` [`--pid-path=`] [`--alsa-volume`] +[`--default-voice=`[]] [`--debug`] [`--help`] [`--version`] + +## OPTIONS + + * `-P` , `--pid-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` , `--default-voice=`: + 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 is the author of espeakup. + +This manual page was written by Chris Brannon . + +current authors and maintainers can be found at +[github](https://github.com/linux-speakup/espeakup/graphs/contributors) diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..f055bb4 --- /dev/null +++ b/doc/meson.build @@ -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 diff --git a/espeakup.c b/espeakup.c deleted file mode 100644 index e8da682..0000000 --- a/espeakup.c +++ /dev/null @@ -1,132 +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 . - */ - -#include -#include -#include -#include -#include -#include - -#include "espeakup.h" - -/* 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; - -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); - - if (!debug) { - if (espeakup_is_running()) { - printf("Espeakup is already running!\n"); - return 1; - } - - /* 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 */ - 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; -} diff --git a/espeakup.h b/espeakup.h deleted file mode 100644 index 2420c64..0000000 --- a/espeakup.h +++ /dev/null @@ -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 . - */ - -#ifndef __ESPEAKUP_H -#define __ESPEAKUP_H - -/* This was added for gcc 4.3 */ -#include - -#include - -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 diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..38788d2 --- /dev/null +++ b/meson.build @@ -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) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..c8560b6 --- /dev/null +++ b/meson_options.txt @@ -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') diff --git a/queue.c b/queue.c deleted file mode 100644 index f7640d0..0000000 --- a/queue.c +++ /dev/null @@ -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 . - */ - -#include -#include -#include -#include - -#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; - } - if (error == EE_OK) - queue_remove(); -} diff --git a/services/meson.build b/services/meson.build new file mode 100644 index 0000000..5eadcac --- /dev/null +++ b/services/meson.build @@ -0,0 +1,4 @@ +systemd = dependency('systemd') +if (systemd.found() and get_option('systemd').allowed()) or get_option('systemd').enabled() + subdir('systemd') +endif diff --git a/services/systemd/espeakup.service.in b/services/systemd/espeakup.service.in new file mode 100644 index 0000000..075a717 --- /dev/null +++ b/services/systemd/espeakup.service.in @@ -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 diff --git a/services/systemd/meson.build b/services/systemd/meson.build new file mode 100644 index 0000000..1a15303 --- /dev/null +++ b/services/systemd/meson.build @@ -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 +) diff --git a/softsynth.c b/softsynth.c deleted file mode 100644 index e1c147b..0000000 --- a/softsynth.c +++ /dev/null @@ -1,182 +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 . - */ - -#include -#include -#include -#include -#include - -#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); - } -} diff --git a/cli.c b/src/cli.c similarity index 56% rename from cli.c rename to src/cli.c index ecb9bb6..8453587 100644 --- a/cli.c +++ b/src/cli.c @@ -1,5 +1,5 @@ /* - * espeakup - interface which allows speakup to use espeak + * espeakup - interface which allows speakup to use espeak-ng * * Copyright (C) 2008 William Hubbs * @@ -20,37 +20,53 @@ #include #include #include +#include #include "espeakup.h" +#include "stringhandling.h" +#include "version.h" -/* program version */ -const char *Version = "0.4"; +/* 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"; +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(" --debug, -d\tDebug mode (stay in the foreground)\n"); - printf(" --help, -h\tShow this help\n"); - printf(" --version, -v\tDisplay the software version.\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"); exit(0); } 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 \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); } @@ -61,6 +77,15 @@ 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 = dupeString(optarg); + break; + case 'a': + espeakup_mode = ESPEAKUP_MODE_ACSINT; + break; case 'd': debug = 1; break; @@ -71,6 +96,7 @@ void process_cli(int argc, char **argv) show_version(); break; case -1: + case 0: break; default: show_help(); diff --git a/src/espeak.c b/src/espeak.c new file mode 100644 index 0000000..c9ac908 --- /dev/null +++ b/src/espeak.c @@ -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 . + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, + " "); + 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 element, which is the source of + * the spurious high-pitched "ringing" on these characters. */ + const char *entity = NULL; + switch (c) { + case '<': entity = "<"; break; + case '>': entity = ">"; break; + case '&': entity = "&"; break; + case '\'': entity = "'"; break; + case '"': entity = """; break; + } + if (entity) + n = asprintf(&buf, + "%s", + entity); + else + n = asprintf(&buf, + "%c", + 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), "", + 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; +} diff --git a/src/espeakup.c b/src/espeakup.c new file mode 100644 index 0000000..4e077f5 --- /dev/null +++ b/src/espeakup.c @@ -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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/espeakup.h b/src/espeakup.h new file mode 100644 index 0000000..62a5ca8 --- /dev/null +++ b/src/espeakup.h @@ -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 . + */ + +#ifndef __ESPEAKUP_H +#define __ESPEAKUP_H + +// This was added for gcc 4.3 +#include +#include + +#include + +#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 diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..6221208 --- /dev/null +++ b/src/meson.build @@ -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') diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 0000000..63a5527 --- /dev/null +++ b/src/queue.c @@ -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 . + */ + +#include +#include + +#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; +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..a1a2a99 --- /dev/null +++ b/src/queue.h @@ -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 . + */ + +#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 diff --git a/src/signal.c b/src/signal.c new file mode 100644 index 0000000..b6ddefc --- /dev/null +++ b/src/signal.c @@ -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 . + */ + +#include +#include +#include +#include + +#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; +} diff --git a/src/softsynth.c b/src/softsynth.c new file mode 100644 index 0000000..e382b32 --- /dev/null +++ b/src/softsynth.c @@ -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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); + } +} diff --git a/src/stringhandling.c b/src/stringhandling.c new file mode 100644 index 0000000..096b870 --- /dev/null +++ b/src/stringhandling.c @@ -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 . + */ + +/* + * 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 +#include +#include + +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; +} diff --git a/src/stringhandling.h b/src/stringhandling.h new file mode 100644 index 0000000..7f104ef --- /dev/null +++ b/src/stringhandling.h @@ -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 . + */ + +#ifndef __STRINGHANDLING_H +#define __STRINGHANDLING_H + +#include + +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 diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 0000000..9ba36a9 --- /dev/null +++ b/src/version.h.in @@ -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 . + */ + +#ifndef __VERSION_H +#define __VERSION_H + +#define PACKAGE_VERSION "@VCS_TAG@" + +#endif diff --git a/synth.c b/synth.c deleted file mode 100644 index 94279fd..0000000 --- a/synth.c +++ /dev/null @@ -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 . - */ - -#include - -#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; -}