diff --git a/espeakup.8 b/espeakup.8 index 092c776..5f65afa 100644 --- a/espeakup.8 +++ b/espeakup.8 @@ -9,10 +9,16 @@ espeakup \(em connect Speakup to the ESpeak TTS engine .SH SYNOPSIS .B espeakup [ -.B \-\^\-pid-path=path +.B \-\^\-default-voice=voicename ] [ -.B \-\^\-default-voice=voicename +.B \-\^\-rate-multiplier=integer +] +[ +.B \-\^\-rate-offset=integer +] +[ +.B \-\^\-range=integer ] [ .B \-\^\-debug @@ -25,12 +31,18 @@ espeakup \(em connect Speakup to the ESpeak TTS engine ] .SH OPTIONS .TP -.B \-P path, \-\^\-pid-path=path -Set the full path for the pid file espeakup uses when in daemon mode. -.TP .B \-V voicename, \-\^\-default-voice=voicename Set the espeak voice to be used by default. .TP +.B \-m integer, \-\^\-rate-multiplier=integer +Set the multiplier to use for calculating Espeak rate. +.TP +.B \-o integer, \-\^\-rate-offset=integer +Set the offset to use for calculating Espeak rate. +.TP +.B \-r integer, \-\^\-default-range=integer +Set the espeak range to be used by default, from 0 to 100. +.TP .B \-d, \-\^\-debug run in the foreground, rather than becoming a daemon process. .TP diff --git a/espeakup.c b/espeakup.c index ffb2e42..fbf291d 100644 --- a/espeakup.c +++ b/espeakup.c @@ -23,23 +23,27 @@ #include #include #include +#include #include "espeakup.h" +/* program version */ +const char *Version = "0.71"; + /* path to our pid file */ -char *pidPath = "/var/run/espeakup.pid"; +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; + +// there is no actual support for adjusting this from within speakup_soft, +// so the unmultiplied value will just be passed to espeak +int defaultRange = 50; +char *defaultVoice = NULL; 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; -pthread_cond_t stop_acknowledged = PTHREAD_COND_INITIALIZER; -pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER; int espeakup_is_running(void) { @@ -71,96 +75,84 @@ int create_pid_file(void) 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) { - sigset_t sigset; - int err; - pthread_t signal_thread_id; - pthread_t espeak_thread_id; - pthread_t softsynth_thread_id; + pthread_t queue_thread_id; struct synth_t s = { .voice = "", }; - synth_queue = new_queue(); - - if (!synth_queue) { - fprintf(stderr, "Unable to allocate memory.\n"); - return 2; - } /* process command line options */ process_cli(argc, argv); - if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP) { - /* Is the espeakup daemon running? */ - if (espeakup_is_running()) { - printf("Espeakup is already running!\n"); - return 1; - } + /* Is the espeakup daemon running? */ + if (espeakup_is_running()) { + printf("Espeakup is already running!\n"); + return 1; + } - /* - * If we are not in debug mode, daemonize and store the pid. - */ - if (!debug) { - daemon(0, 1); - if (create_pid_file() < 0) { - perror("Unable to create pid file"); - return 2; - } + /* 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; } } - /* set up the pipe used to wake the espeak thread */ - if (pipe(self_pipe_fds) < 0) { - perror("Unable to create pipe"); - return 5; - } + /* initialize espeak */ + espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, NULL, 0); - /* create the signal processing thread here. */ - err = pthread_create(&signal_thread_id, NULL, signal_thread, NULL); + /* 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_range(&s, defaultRange, 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; } - /* - * 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); + /* run the main loop */ + main_loop(&s); -/* Initialize espeak */ - if (initialize_espeak(&s) < 0) { - return 2; - } - -/* open the softsynth */ - if (open_softsynth() < 0) { - return 2; - } - - /* Spawn our softsynth thread. */ - err = pthread_create(&softsynth_thread_id, NULL, softsynth_thread, &s); - if (err != 0) { - return 4; - } - - /* Spawn our espeak-interacting thread. */ - err = pthread_create(&espeak_thread_id, NULL, espeak_thread, &s); - if (err != 0) { - return 4; - } - - /* wait for the threads to shut down. */ - pthread_join(signal_thread_id, NULL); - pthread_join(softsynth_thread_id, NULL); - pthread_join(espeak_thread_id, NULL); - - espeak_Terminate(); - close_softsynth(); - if (!debug && espeakup_mode == ESPEAKUP_MODE_SPEAKUP) - unlink(pidPath); return 0; } diff --git a/espeakup.h b/espeakup.h index 57a3f29..a0caabf 100644 --- a/espeakup.h +++ b/espeakup.h @@ -22,23 +22,13 @@ /* This was added for gcc 4.3 */ #include -#include #include -#include "queue.h" - -#define PACKAGE_VERSION "0.80-dev" -#define PACKAGE_BUGREPORT "http://github.com/williamh/espeakup/issues" - -enum espeakup_mode_t { - ESPEAKUP_MODE_SPEAKUP, - ESPEAKUP_MODE_ACSINT -}; - enum command_t { CMD_SET_FREQUENCY, CMD_SET_PITCH, + CMD_SET_RANGE, CMD_SET_PUNCTUATION, CMD_SET_RATE, CMD_SET_VOICE, @@ -54,17 +44,10 @@ enum adjust_t { 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[10]; @@ -73,25 +56,31 @@ struct synth_t { 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_range(struct synth_t *s, int range, + 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 volatile int should_run; -extern volatile int stop_requested; -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 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/queue.c b/queue.c index 860ad94..4594d5b 100644 --- a/queue.c +++ b/queue.c @@ -1,10 +1,6 @@ /* * espeakup - interface which allows speakup to use espeak * - * Note that these functions are meant to be used in either a single or - * multi-threaded environment, so they know nothing about mutexes, etc. - * Handling this is up to the caller. - * * Copyright (C) 2008 William Hubbs * * This program is free software: you can redistribute it and/or modify @@ -22,67 +18,192 @@ */ #include +#include #include +#include +#include -#include "stringhandling.h" +#include "espeakup.h" + +pthread_cond_t runner_awake = PTHREAD_COND_INITIALIZER; +pthread_mutex_t queue_guard = PTHREAD_MUTEX_INITIALIZER; struct queue_entry_t { - void *data; + enum command_t cmd; + enum adjust_t adjust; + int value; + char *buf; + int len; struct queue_entry_t *next; }; -struct queue_t { - struct queue_entry_t *head; - struct queue_entry_t *tail; -}; +static struct queue_entry_t *first = NULL; +static struct queue_entry_t *last = NULL; -struct queue_t *new_queue(void) +static void queue_add(struct queue_entry_t *entry) { - 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; + pthread_mutex_lock(&queue_guard); + assert(entry); + entry->next = NULL; + if (!last) + last = entry; + if (!first) { + first = entry; } else { - q->tail->next = tmp; - q->tail = q->tail->next; + first->next = entry; + first = first->next; } - if (!q->head) - q->head = tmp; - return 1; + pthread_mutex_unlock(&queue_guard); + pthread_cond_signal(&runner_awake); } -void *queue_remove(struct queue_t *q) +static void free_entry(struct queue_entry_t *entry) { - void *data = NULL; - struct queue_entry_t *tmp; + if (entry->cmd == CMD_SPEAK_TEXT) + free(entry->buf); + free(entry); +} - if (q->head) { - tmp = q->head; - data = tmp->data; - q->head = tmp->next; - free(tmp); - if (!q->head) - q->tail = q->head; +/* 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 data; + + return temp; } -void *queue_peek(struct queue_t *q) +void queue_clear(void) { - if (q->head) - return q->head->data; - else - return NULL; + 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_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: + 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; }