diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 16c7f08..0000000 --- a/.clang-format +++ /dev/null @@ -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 -... diff --git a/.gitignore b/.gitignore index 1b2211d..49299b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build* +espeakup +*.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b636930 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +INSTALL = install + +SRCS = \ + cli.c \ + espeakup.c \ + queue.c \ + softsynth.c \ + synth.c + +OBJS = $(SRCS:.c=.o) + +LDLIBS = -lespeak -lpthread + +PREFIX = /usr +MANDIR = $(PREFIX)/share/man/man8 +BINDIR = $(PREFIX)/bin + +all: espeakup + +install: espeakup + $(INSTALL) -d $(DESTDIR)$(BINDIR) + $(INSTALL) -m 0755 $< $(DESTDIR)$(BINDIR) + $(INSTALL) -d $(DESTDIR)$(MANDIR) + $(INSTALL) -m 0644 espeakup.8 $(DESTDIR)$(MANDIR) + +clean: + $(RM) $(OBJS) + +distclean: clean + $(RM) espeakup + +espeakup: $(OBJS) + +cli.o: cli.c espeakup.h + +espeakup.o: espeakup.c espeakup.h + +queue.o: queue.c espeakup.h + +softsynth.o: softsynth.c espeakup.h + +synth.o: synth.c espeakup.h + +%.o: %.c + $(CC) -c -Wall $(CFLAGS) $(CPPFLAGS) -o $@ $< diff --git a/README b/README new file mode 100644 index 0000000..317db39 --- /dev/null +++ b/README @@ -0,0 +1,71 @@ +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, here are some basic instructions. + +To install espeakup, first cd into the directory where you +unpacked the tarball, then issue make to compile the program. Once this +is done, as root, issue make install. This will install espeakup in /usr/bin. + +Starting Up +=========== + +This program should be run after speakup is set up to communicate with a +software synthesizer and after /dev/softsynth exists. The way this is +done is distribution specific, so it is beyond the scope of this +documentation. + +Command Line Options +==================== + +Espeakup currently accepts the following command line options: + + --default-voice=voice, -V voice Set default voice. + --debug, -d Debug mode (stay in the foreground). + --help, -h Show this help. + --version, -v Display the software version. + +Getting the Latest Version +========================== + +Currently, a tarball of the latest official release will be included in +the contrib/ directory of the speakup repository. Also, it is possible +to download a tarball of any released version from github as follows: + +wget http://www.github.com/williamh/espeakup/tarball/vx.y + +Also, the clone URL, if you are familiar with using git 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. diff --git a/README.md b/README.md deleted file mode 100644 index e1395b0..0000000 --- a/README.md +++ /dev/null @@ -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. diff --git a/ToDo b/ToDo new file mode 100644 index 0000000..ec1d210 --- /dev/null +++ b/ToDo @@ -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. + diff --git a/src/cli.c b/cli.c similarity index 70% rename from src/cli.c rename to cli.c index 8453587..023d6cc 100644 --- a/src/cli.c +++ b/cli.c @@ -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,37 +23,28 @@ #include #include "espeakup.h" -#include "stringhandling.h" -#include "version.h" -/* pid path */ -extern char *pidPath; +/* program version */ +extern const char *Version; /* default voice */ extern char *defaultVoice; -/* Whether to drive ALSA volume */ -extern int alsaVolume; - /* command line options */ -const char *shortOptions = "P:V:adhv"; +const char *shortOptions = "dhV:v"; const struct option longOptions[] = { - {"pid-path", required_argument, NULL, 'P'}, {"default-voice", required_argument, NULL, 'V'}, - {"alsa-volume", no_argument, &alsaVolume, 1}, - {"acsint", no_argument, NULL, 'a'}, {"debug", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, - {0, 0, 0, 0}}; + {0, 0, 0, 0} +}; static void show_help() { printf("Usage: espeakup [options]\n\n"); printf("Options are as follows:\n"); - printf(" --pid-path=path, -P path\t\tSet path for pid file.\n"); printf(" --default-voice=voice, -V voice\tSet default voice.\n"); - printf(" --alsa-volume\t\t\t\tDrive the ALSA volume.\n"); printf(" --debug, -d\t\t\t\tDebug mode (stay in the foreground).\n"); printf(" --help, -h\t\t\t\tShow this help.\n"); printf(" --version, -v\t\t\t\tDisplay the software version.\n"); @@ -62,11 +53,10 @@ static void show_help() static void show_version(void) { - printf("ESpeakup %s\n", PACKAGE_VERSION); - printf("Copyright (C) 2008 William Hubbs \n"); + printf("espeakup %s\n", 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); } @@ -77,14 +67,8 @@ 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; + defaultVoice = strdup(optarg); break; case 'd': debug = 1; @@ -96,7 +80,6 @@ void process_cli(int argc, char **argv) show_version(); break; case -1: - case 0: break; default: show_help(); diff --git a/doc/espeakup.8.ronn b/doc/espeakup.8.ronn deleted file mode 100644 index 598611c..0000000 --- a/doc/espeakup.8.ronn +++ /dev/null @@ -1,77 +0,0 @@ - - -# 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 deleted file mode 100644 index f055bb4..0000000 --- a/doc/meson.build +++ /dev/null @@ -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 diff --git a/espeakup.8 b/espeakup.8 new file mode 100644 index 0000000..314aed7 --- /dev/null +++ b/espeakup.8 @@ -0,0 +1,72 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.\" Espeakup is Copyright 2008 by William Hubbs. +.\" This is free software; see the GNU General Public Licence version 3 +.\" or later for copying conditions. There is NO warranty. +.TH ESPEAKUP 8 "5 Nov 2008" "0.60" +.nh +.SH NAME +espeakup \(em connect Speakup to the ESpeak TTS engine +.SH SYNOPSIS +.B espeakup +[ +.B \-\^\-default-voice=voicename +] +[ +.B \-\^\-debug +] +[ +.B \-\^\-help +] +[ +.B \-\^\-version +] +.SH OPTIONS +.TP +.B \-V voicename, \-\^\-default-voice=voicename +Set the espeak voice to be used by default. +.TP +.B \-d, \-\^\-debug +run in the foreground, rather than becoming a daemon process. +.TP +.B \-h, \-\^\-help +display a brief help message and exit. +.TP +.B \-v, \-\^\-version +output version information and exit. +.SH DESCRIPTION +Espeakup bridges the gap between two tools: the Speakup screen review +system and the ESppeak text-to-speech engine. Each of these tools +performs a well-defined task. Speakup is a kernel-based screen reader +for the Linux console. It extracts and processes the text that is +displayed on the foreground virtual console. It supports several +hardware based speech synthesizers directly. However, since it is in +kernel space, it cannot support a software speech synthesizer directly +since these are in user space. +ESpeak is a popular software speech synthesizer. It is small, light +weight, very responsive, and supports multiple languages. +Espeakup is a connector which will read text sent to it by speakup and +forward it to ESpeak. This allows Speakup to use ESpeak as its speech +synthesizer. +.PP +Espeakup is a daemon. Typically, it is started at boot time, and it terminates +when the system is halted or rebooted. It should be started by the +system's init scripts. This process varies among Linux distributions, +but the details are usually managed by the person who packaged Espeakup for +your distribution. +From the perspective of an average user, Espeakup's operation is invisible. +.SH BUGS +.PP +Espeakup is still classified as alpha software. Bugs are periodically found +and fixed. If you find a bug, please do report it to the author. You +might also consider mentioning it on the mailing list for the Speakup +screenreader. Visit http://speech.braille.uwo.ca/mailman/listinfo/speakup +to learn more about the mailing list. +.SH SEE ALSO +.PP +For more information about Speakup, visit its homepage: http://linux-speakup.org. +ESpeak's home page is http://espeak.sourceforge.net. +.SH AUTHOR +.PP +William Hubbs is the author and maintainer of Espeakup. He may be reached +via the email address . This manual page was written +by Chris Brannon, and his email address is . diff --git a/espeakup.c b/espeakup.c new file mode 100644 index 0000000..7157b0a --- /dev/null +++ b/espeakup.c @@ -0,0 +1,154 @@ +/* + * 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 + +#include "espeakup.h" + +/* program version */ +const char *Version = "0.71"; + +/* path to our pid file */ +const char *pidPath = "/var/run/espeakup.pid"; + +/* default voice settings */ +const int defaultFrequency = 5; +const int defaultPitch = 5; +const int defaultRate = 5; +const int defaultVolume = 5; + +char *defaultVoice = NULL; +int debug = 0; + +int espeakup_is_running(void) +{ + int rc; + FILE *pidFile; + pid_t pid; + + rc = 0; + pidFile = fopen(pidPath, "r"); + if (pidFile) { + fscanf(pidFile, "%d", &pid); + fclose(pidFile); + if (!kill(pid, 0) || errno != ESRCH) + rc = 1; + } + return rc; +} + +int create_pid_file(void) +{ + FILE *pidFile; + + pidFile = fopen(pidPath, "w"); + if (!pidFile) + return -1; + + fprintf(pidFile, "%d\n", getpid()); + fclose(pidFile); + return 0; +} + +void espeakup_sighandler(int sig) +{ + if (debug) + printf("Caught signal %i\n", sig); + + /* clear the queue */ + queue_clear(); + + /* shut down espeak and close the softsynth */ + espeak_Terminate(); + close_softsynth(); + + if (!debug) + unlink(pidPath); + exit(0); +} + +int main(int argc, char **argv) +{ + pthread_t queue_thread_id; + struct synth_t s = { + .voice = "", + }; + + /* process command line options */ + process_cli(argc, argv); + + /* Is the espeakup daemon running? */ + if (espeakup_is_running()) { + printf("Espeakup is already running!\n"); + return 1; + } + + /* open the softsynth. */ + if (! open_softsynth()) { + perror("Unable to open the softsynth device"); + return 3; + } + + /* register signal handler */ + signal(SIGINT, espeakup_sighandler); + signal(SIGTERM, espeakup_sighandler); + + if (!debug) { + /* become a daemon */ + daemon(0, 1); + + /* write our pid file. */ + if (create_pid_file() < 0) { + perror("Unable to create pid file"); + return 2; + } + } + + /* initialize espeak */ + espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0); + + /* Setup initial voice parameters */ + if (defaultVoice) { + set_voice(&s, defaultVoice); + free(defaultVoice); + defaultVoice = NULL; + } + set_frequency(&s, defaultFrequency, ADJ_SET); + set_pitch(&s, defaultPitch, ADJ_SET); + set_rate(&s, defaultRate, ADJ_SET); + set_volume(&s, defaultVolume, ADJ_SET); + espeak_SetParameter(espeakCAPITALS, 0, 0); + + /* Spawn our queue-processing thread. */ + int err = pthread_create(&queue_thread_id, NULL, &queue_runner, &s); + if (err != 0) { + return 4; + } + + /* run the main loop */ + main_loop(&s); + + return 0; +} diff --git a/src/espeakup.h b/espeakup.h similarity index 50% rename from src/espeakup.h rename to espeakup.h index 62a5ca8..75292c8 100644 --- a/src/espeakup.h +++ b/espeakup.h @@ -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,87 +20,63 @@ #ifndef __ESPEAKUP_H #define __ESPEAKUP_H -// This was added for gcc 4.3 -#include +/* 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 -{ +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, }; -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]; + char voice[10]; 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 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 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 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; +extern void main_loop(struct synth_t *s); +extern void * queue_runner(void *arg); #endif diff --git a/meson.build b/meson.build deleted file mode 100644 index 38788d2..0000000 --- a/meson.build +++ /dev/null @@ -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) diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index c8560b6..0000000 --- a/meson_options.txt +++ /dev/null @@ -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') diff --git a/queue.c b/queue.c new file mode 100644 index 0000000..83fb979 --- /dev/null +++ b/queue.c @@ -0,0 +1,206 @@ +/* + * 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" + +pthread_cond_t runner_awake = PTHREAD_COND_INITIALIZER; +pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER; + +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) +{ + pthread_mutex_lock(&queue_guard); + assert(entry); + entry->next = NULL; + if (!last) + last = entry; + if (!first) { + first = entry; + } else { + first->next = entry; + first = first->next; + } + pthread_mutex_unlock(&queue_guard); + pthread_cond_signal(&runner_awake); +} + +static void free_entry(struct queue_entry_t *entry) +{ + if (entry->cmd == CMD_SPEAK_TEXT) + free(entry->buf); + free(entry); +} + +/* Remove and return the entry at the head of the queue. + * Return NULL if queue is empty. */ + +static struct queue_entry_t *queue_remove(void) +{ + struct queue_entry_t *temp = NULL; + + if (last) { + temp = last; + last = temp->next; + + if (!last) + first = last; + } + + return temp; +} + +void queue_clear(void) +{ + pthread_mutex_lock(&queue_guard); + while (last) { + struct queue_entry_t *entry = queue_remove(); + if (entry) + free_entry(entry); + } + pthread_mutex_unlock(&queue_guard); + /* We aren't adding data to the queue, so no need to signal. */ +} + +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); +} + +static void queue_process_entry(struct synth_t *s) +{ + espeak_ERROR error; + struct queue_entry_t *current = queue_remove(); + + pthread_mutex_unlock(&queue_guard); /* So "reader" can go. */ + + if (current) { + 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: + 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; + } + + free_entry(current); + } +} + +/* queue_runner 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 *queue_runner(void *arg) +{ + struct synth_t *synth = (struct synth_t *) arg; + pthread_mutex_lock(&queue_guard); + while (1) { + pthread_cond_wait(&runner_awake, &queue_guard); + + while (last) { + queue_process_entry(synth); + pthread_mutex_lock(&queue_guard); + } + } + + return NULL; +} diff --git a/services/meson.build b/services/meson.build deleted file mode 100644 index 5eadcac..0000000 --- a/services/meson.build +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 075a717..0000000 --- a/services/systemd/espeakup.service.in +++ /dev/null @@ -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 diff --git a/services/systemd/meson.build b/services/systemd/meson.build deleted file mode 100644 index 1a15303..0000000 --- a/services/systemd/meson.build +++ /dev/null @@ -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 -) diff --git a/softsynth.c b/softsynth.c new file mode 100644 index 0000000..b482ec9 --- /dev/null +++ b/softsynth.c @@ -0,0 +1,183 @@ +/* + * 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 + +#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 ((buf[end] < 0 || 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; + } +} + +int open_softsynth(void) +{ + int rc; + + softFD = open("/dev/softsynth", O_RDWR | O_NONBLOCK); + if (softFD < 0) + rc = 0; + else + rc = 1; + return rc; +} + +void close_softsynth(void) +{ + close(softFD); +} + +void main_loop(struct synth_t *s) +{ + fd_set set; + ssize_t length; + char buf[maxBufferSize]; + char *cp; + + while (1) { + + FD_ZERO(&set); + FD_SET(softFD, &set); + if (select(softFD + 1, &set, NULL, NULL, NULL) < 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/src/espeak.c b/src/espeak.c deleted file mode 100644 index c9ac908..0000000 --- a/src/espeak.c +++ /dev/null @@ -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 . - */ - -#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 deleted file mode 100644 index 4e077f5..0000000 --- a/src/espeakup.c +++ /dev/null @@ -1,260 +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 . - */ - -#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/meson.build b/src/meson.build deleted file mode 100644 index 6221208..0000000 --- a/src/meson.build +++ /dev/null @@ -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') diff --git a/src/queue.c b/src/queue.c deleted file mode 100644 index 63a5527..0000000 --- a/src/queue.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 deleted file mode 100644 index a1a2a99..0000000 --- a/src/queue.h +++ /dev/null @@ -1,30 +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 . - */ - -#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 deleted file mode 100644 index b6ddefc..0000000 --- a/src/signal.c +++ /dev/null @@ -1,81 +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 . - */ - -#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 deleted file mode 100644 index e382b32..0000000 --- a/src/softsynth.c +++ /dev/null @@ -1,370 +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 . - */ - -#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 deleted file mode 100644 index 096b870..0000000 --- a/src/stringhandling.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 deleted file mode 100644 index 7f104ef..0000000 --- a/src/stringhandling.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 deleted file mode 100644 index 9ba36a9..0000000 --- a/src/version.h.in +++ /dev/null @@ -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 . - */ - -#ifndef __VERSION_H -#define __VERSION_H - -#define PACKAGE_VERSION "@VCS_TAG@" - -#endif diff --git a/synth.c b/synth.c new file mode 100644 index 0000000..fe6ca15 --- /dev/null +++ b/synth.c @@ -0,0 +1,127 @@ +/* + * 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; +} diff --git a/tarball b/tarball new file mode 100755 index 0000000..12bf7ee --- /dev/null +++ b/tarball @@ -0,0 +1,22 @@ +#!/bin/bash +# Makes a tarball release + +VER=$(./version) +PREFIX=espeakup-${VER} +REL=${1:-v${VER}} + +if [ "$REL" != "v${VER}" ]; then + TIMESTAMP=`git show $REL --pretty=format:%ai |head -1` + PATCHLEVEL=`date --utc -d "$TIMESTAMP" +_p%Y%m%d%H%M` +fi + +TARFILE=${PREFIX}${PATCHLEVEL}.tar + +git archive --format=tar --prefix=${PREFIX}/ $REL > ${TARFILE} +tar f ${TARFILE} --delete ${PREFIX}/.gitignore --delete ${PREFIX}/tarball +mkdir ${PREFIX} +git log ${REL} > ${PREFIX}/ChangeLog +tar rf ${TARFILE} ${PREFIX} +rm -rf ${PREFIX} +bzip2 ${TARFILE} +echo "Produced ${TARFILE}.bz2" diff --git a/version b/version new file mode 100755 index 0000000..de85fd8 --- /dev/null +++ b/version @@ -0,0 +1,5 @@ +#!/bin/bash +ver=$(grep "const char.*Version" espeakup.c) +ver=${ver%\"*} +ver=${ver#*\"} +echo ${ver}