| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "parameters.h" |
| #include "log.h" |
| #include "strfmt.h" |
| #include "prefs.h" |
| #include "spk_thread.h" |
| #include "spk.h" |
| #include "async_wait.h" |
| #include "async_event.h" |
| #include "thread.h" |
| #include "queue.h" |
| |
| #ifdef ENABLE_SPEECH_SUPPORT |
| typedef enum { |
| THD_CONSTRUCTING, |
| THD_STARTING, |
| THD_READY, |
| THD_STOPPING, |
| THD_FINISHED |
| } ThreadState; |
| |
| typedef struct { |
| const char *name; |
| } ThreadStateEntry; |
| |
| static const ThreadStateEntry threadStateTable[] = { |
| [THD_CONSTRUCTING] = { |
| .name = "constructing" |
| }, |
| |
| [THD_STARTING] = { |
| .name = "starting" |
| }, |
| |
| [THD_READY] = { |
| .name = "ready" |
| }, |
| |
| [THD_STOPPING] = { |
| .name = "stopping" |
| }, |
| |
| [THD_FINISHED] = { |
| .name = "finished" |
| }, |
| }; |
| |
| static inline const ThreadStateEntry * |
| getThreadStateEntry (ThreadState state) { |
| if (state >= ARRAY_COUNT(threadStateTable)) return NULL; |
| return &threadStateTable[state]; |
| } |
| |
| typedef enum { |
| RSP_PENDING, |
| RSP_INTEGER |
| } SpeechResponseType; |
| |
| struct SpeechDriverThreadStruct { |
| ThreadState threadState; |
| Queue *requestQueue; |
| |
| SpeechSynthesizer *speechSynthesizer; |
| char **driverParameters; |
| |
| #ifdef GOT_PTHREADS |
| pthread_t threadIdentifier; |
| AsyncEvent *requestEvent; |
| AsyncEvent *messageEvent; |
| unsigned isBeingDestroyed:1; |
| #endif /* GOT_PTHREADS */ |
| |
| struct { |
| SpeechResponseType type; |
| |
| union { |
| int INTEGER; |
| } value; |
| } response; |
| }; |
| |
| typedef enum { |
| REQ_SAY_TEXT, |
| REQ_MUTE_SPEECH, |
| REQ_DRAIN_SPEECH, |
| |
| REQ_SET_VOLUME, |
| REQ_SET_RATE, |
| REQ_SET_PITCH, |
| REQ_SET_PUNCTUATION |
| } SpeechRequestType; |
| |
| static const char *const speechRequestNames[] = { |
| [REQ_SAY_TEXT] = "say text", |
| [REQ_MUTE_SPEECH] = "mute speech", |
| [REQ_DRAIN_SPEECH] = "drain speech", |
| |
| [REQ_SET_VOLUME] = "set volume", |
| [REQ_SET_RATE] = "set rate", |
| [REQ_SET_PITCH] = "set pitch", |
| [REQ_SET_PUNCTUATION] = "set punctuation" |
| }; |
| |
| typedef struct { |
| SpeechRequestType type; |
| |
| union { |
| struct { |
| const unsigned char *text; |
| size_t length; |
| size_t count; |
| const unsigned char *attributes; |
| SayOptions options; |
| } sayText; |
| |
| struct { |
| unsigned char setting; |
| } setVolume; |
| |
| struct { |
| unsigned char setting; |
| } setRate; |
| |
| struct { |
| unsigned char setting; |
| } setPitch; |
| |
| struct { |
| SpeechPunctuation setting; |
| } setPunctuation; |
| } arguments; |
| |
| unsigned char data[0]; |
| } SpeechRequest; |
| |
| typedef struct { |
| const void *address; |
| size_t size; |
| unsigned end:1; |
| } SpeechDatum; |
| |
| #define BEGIN_SPEECH_DATA SpeechDatum data[] = { |
| #define END_SPEECH_DATA {.end=1} }; |
| |
| typedef enum { |
| MSG_REQUEST_FINISHED, |
| MSG_SPEECH_FINISHED, |
| MSG_SPEECH_LOCATION |
| } SpeechMessageType; |
| |
| static const char *const speechMessageNames[] = { |
| [MSG_REQUEST_FINISHED] = "request finished", |
| [MSG_SPEECH_FINISHED] = "speech finished", |
| [MSG_SPEECH_LOCATION] = "speech location" |
| }; |
| |
| typedef struct { |
| SpeechMessageType type; |
| |
| union { |
| struct { |
| int result; |
| } requestFinished; |
| |
| struct { |
| int location; |
| } speechLocation; |
| } arguments; |
| |
| unsigned char data[0]; |
| } SpeechMessage; |
| |
| static const char * |
| getActionName (unsigned int action, const char *const *names, size_t count) { |
| return (action < count)? names[action]: NULL; |
| } |
| |
| typedef struct { |
| const char *action; |
| const char *type; |
| const char *name; |
| unsigned int value; |
| } LogSpeechActionData; |
| |
| static |
| STR_BEGIN_FORMATTER(formatLogSpeechActionData, const void *data) |
| const LogSpeechActionData *lsa = data; |
| |
| STR_PRINTF("%s speech %s: ", lsa->action, lsa->type); |
| |
| if (lsa->name) { |
| STR_PRINTF("%s", lsa->name); |
| } else { |
| STR_PRINTF("%u", lsa->value); |
| } |
| STR_END_FORMATTER |
| |
| static void |
| logSpeechAction (const LogSpeechActionData *lsa) { |
| logData(LOG_CATEGORY(SPEECH_EVENTS), formatLogSpeechActionData, lsa); |
| } |
| |
| static void |
| logSpeechRequest (SpeechRequest *req, const char *action) { |
| const LogSpeechActionData lsa = { |
| .action = action, |
| .type = "request", |
| .name = req? getActionName(req->type, speechRequestNames, ARRAY_COUNT(speechRequestNames)): "stop", |
| .value = req? req->type: 0 |
| }; |
| |
| logSpeechAction(&lsa); |
| } |
| |
| static void |
| logSpeechMessage (SpeechMessage *msg, const char *action) { |
| const LogSpeechActionData lsa = { |
| .action = action, |
| .type = "message", |
| .name = getActionName(msg->type, speechMessageNames, ARRAY_COUNT(speechMessageNames)), |
| .value = msg->type |
| }; |
| |
| logSpeechAction(&lsa); |
| } |
| |
| static int |
| testThreadValidity (SpeechDriverThread *sdt) { |
| if (!sdt) return 0; |
| |
| #ifdef GOT_PTHREADS |
| if (sdt->isBeingDestroyed) return 0; |
| #endif /* GOT_PTHREADS */ |
| |
| SpeechSynthesizer *spk = sdt->speechSynthesizer; |
| if (!spk) return 0; |
| if (sdt != spk->driver.thread) return 0; |
| |
| if (sdt->threadState != THD_READY) return 0; |
| return 1; |
| } |
| |
| static void |
| setThreadState (SpeechDriverThread *sdt, ThreadState state) { |
| const ThreadStateEntry *entry = getThreadStateEntry(state); |
| const char *name = entry? entry->name: NULL; |
| |
| if (!name) name = "?"; |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread %s", name); |
| sdt->threadState = state; |
| } |
| |
| static size_t |
| getSpeechDataSize (const SpeechDatum *data) { |
| size_t size = 0; |
| |
| if (data) { |
| const SpeechDatum *datum = data; |
| |
| while (!datum->end) { |
| if (datum->address) size += datum->size; |
| datum += 1; |
| } |
| } |
| |
| return size; |
| } |
| |
| static void |
| moveSpeechData (unsigned char *target, SpeechDatum *data) { |
| if (data) { |
| SpeechDatum *datum = data; |
| |
| while (!datum->end) { |
| if (datum->address) { |
| memcpy(target, datum->address, datum->size); |
| datum->address = target; |
| target += datum->size; |
| } |
| |
| datum += 1; |
| } |
| } |
| } |
| |
| static inline void |
| setResponsePending (SpeechDriverThread *sdt) { |
| sdt->response.type = RSP_PENDING; |
| } |
| |
| static void |
| setIntegerResponse (SpeechDriverThread *sdt, int value) { |
| sdt->response.type = RSP_INTEGER; |
| sdt->response.value.INTEGER = value; |
| } |
| |
| ASYNC_CONDITION_TESTER(testSpeechResponseReceived) { |
| SpeechDriverThread *sdt = data; |
| |
| return sdt->response.type != RSP_PENDING; |
| } |
| |
| static int |
| awaitSpeechResponse (SpeechDriverThread *sdt, int timeout) { |
| return asyncAwaitCondition(timeout, testSpeechResponseReceived, sdt); |
| } |
| |
| static void sendSpeechRequest (SpeechDriverThread *sdt); |
| |
| static void |
| handleSpeechMessage (SpeechDriverThread *sdt, SpeechMessage *msg) { |
| logSpeechMessage(msg, "handling"); |
| |
| if (msg) { |
| switch (msg->type) { |
| case MSG_REQUEST_FINISHED: |
| setIntegerResponse(sdt, msg->arguments.requestFinished.result); |
| sendSpeechRequest(sdt); |
| break; |
| |
| case MSG_SPEECH_FINISHED: { |
| SpeechSynthesizer *spk = sdt->speechSynthesizer; |
| SetSpeechFinishedMethod *setFinished = spk->setFinished; |
| |
| if (setFinished) setFinished(spk); |
| break; |
| } |
| |
| case MSG_SPEECH_LOCATION: { |
| SpeechSynthesizer *spk = sdt->speechSynthesizer; |
| SetSpeechLocationMethod *setLocation = spk->setLocation; |
| |
| if (setLocation) setLocation(spk, msg->arguments.speechLocation.location); |
| break; |
| } |
| |
| default: |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "unimplemented message: %u", msg->type); |
| break; |
| } |
| |
| free(msg); |
| } |
| } |
| |
| static int |
| sendSpeechMessage (SpeechDriverThread *sdt, SpeechMessage *msg) { |
| logSpeechMessage(msg, "sending"); |
| |
| #ifdef GOT_PTHREADS |
| return asyncSignalEvent(sdt->messageEvent, msg); |
| #else /* GOT_PTHREADS */ |
| handleSpeechMessage(sdt, msg); |
| return 1; |
| #endif /* GOT_PTHREADS */ |
| } |
| |
| static SpeechMessage * |
| newSpeechMessage (SpeechMessageType type, SpeechDatum *data) { |
| SpeechMessage *msg; |
| size_t size = sizeof(*msg) + getSpeechDataSize(data); |
| |
| if ((msg = malloc(size))) { |
| memset(msg, 0, sizeof(*msg)); |
| msg->type = type; |
| moveSpeechData(msg->data, data); |
| return msg; |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| speechMessage_requestFinished ( |
| SpeechDriverThread *sdt, |
| int result |
| ) { |
| SpeechMessage *msg; |
| |
| if ((msg = newSpeechMessage(MSG_REQUEST_FINISHED, NULL))) { |
| msg->arguments.requestFinished.result = result; |
| if (sendSpeechMessage(sdt, msg)) return 1; |
| |
| free(msg); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechMessage_speechFinished ( |
| SpeechDriverThread *sdt |
| ) { |
| SpeechMessage *msg; |
| |
| if ((msg = newSpeechMessage(MSG_SPEECH_FINISHED, NULL))) { |
| if (sendSpeechMessage(sdt, msg)) return 1; |
| |
| free(msg); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechMessage_speechLocation ( |
| SpeechDriverThread *sdt, |
| int location |
| ) { |
| SpeechMessage *msg; |
| |
| if ((msg = newSpeechMessage(MSG_SPEECH_LOCATION, NULL))) { |
| msg->arguments.speechLocation.location = location; |
| if (sendSpeechMessage(sdt, msg)) return 1; |
| |
| free(msg); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| sendIntegerResponse (SpeechDriverThread *sdt, int result) { |
| return speechMessage_requestFinished(sdt, result); |
| } |
| |
| static void |
| handleSpeechRequest (SpeechDriverThread *sdt, SpeechRequest *req) { |
| SpeechSynthesizer *spk = sdt->speechSynthesizer; |
| |
| logSpeechRequest(req, "handling"); |
| |
| if (req) { |
| switch (req->type) { |
| case REQ_SAY_TEXT: { |
| SayOptions options = req->arguments.sayText.options; |
| int restorePitch = 0; |
| int restorePunctuation = 0; |
| |
| if (options & SAY_OPT_MUTE_FIRST) speech->mute(spk); |
| |
| if (options & SAY_OPT_HIGHER_PITCH) { |
| if (spk->setPitch) { |
| unsigned char pitch = prefs.speechPitch + 7; |
| |
| if (pitch > SPK_PITCH_MAXIMUM) pitch = SPK_PITCH_MAXIMUM; |
| |
| if (pitch != prefs.speechPitch) { |
| spk->setPitch(spk, pitch); |
| restorePitch = 1; |
| } |
| } |
| } |
| |
| if (options & SAY_OPT_ALL_PUNCTUATION) { |
| if (spk->setPunctuation) { |
| unsigned char punctuation = SPK_PUNCTUATION_ALL; |
| |
| if (punctuation != prefs.speechPunctuation) { |
| spk->setPunctuation(spk, punctuation); |
| restorePunctuation = 1; |
| } |
| } |
| } |
| |
| speech->say(spk, |
| req->arguments.sayText.text, req->arguments.sayText.length, |
| req->arguments.sayText.count, req->arguments.sayText.attributes |
| ); |
| |
| if (restorePunctuation) spk->setPunctuation(spk, prefs.speechPunctuation); |
| if (restorePitch) spk->setPitch(spk, prefs.speechPitch); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| case REQ_MUTE_SPEECH: { |
| speech->mute(spk); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| case REQ_DRAIN_SPEECH: { |
| spk->drain(spk); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| case REQ_SET_VOLUME: { |
| spk->setVolume(spk, req->arguments.setVolume.setting); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| case REQ_SET_RATE: { |
| spk->setRate(spk, req->arguments.setRate.setting); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| case REQ_SET_PITCH: { |
| spk->setPitch(spk, req->arguments.setPitch.setting); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| case REQ_SET_PUNCTUATION: { |
| spk->setPunctuation(spk, req->arguments.setPunctuation.setting); |
| |
| sendIntegerResponse(sdt, 1); |
| break; |
| } |
| |
| default: |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "unimplemented request: %u", req->type); |
| sendIntegerResponse(sdt, 0); |
| break; |
| } |
| |
| free(req); |
| } else { |
| setThreadState(sdt, THD_STOPPING); |
| sendIntegerResponse(sdt, 1); |
| } |
| } |
| |
| typedef struct { |
| SpeechRequestType const type; |
| } TestSpeechRequestData; |
| |
| static int |
| testSpeechRequest (const void *item, void *data) { |
| const SpeechRequest *req = item; |
| const TestSpeechRequestData *tsr = data; |
| |
| return req->type == tsr->type; |
| } |
| |
| static Element * |
| findSpeechRequestElement (SpeechDriverThread *sdt, SpeechRequestType type) { |
| TestSpeechRequestData tsr = { |
| .type = type |
| }; |
| |
| if (!testThreadValidity(sdt)) return NULL; |
| return findElement(sdt->requestQueue, testSpeechRequest, &tsr); |
| } |
| |
| static void |
| removeSpeechRequests (SpeechDriverThread *sdt, SpeechRequestType type) { |
| Element *element; |
| |
| while ((element = findSpeechRequestElement(sdt, type))) deleteElement(element); |
| } |
| |
| static void |
| muteSpeechRequestQueue (SpeechDriverThread *sdt) { |
| removeSpeechRequests(sdt, REQ_SAY_TEXT); |
| removeSpeechRequests(sdt, REQ_MUTE_SPEECH); |
| } |
| |
| static void |
| sendSpeechRequest (SpeechDriverThread *sdt) { |
| while (getQueueSize(sdt->requestQueue) > 0) { |
| SpeechRequest *req = dequeueItem(sdt->requestQueue); |
| |
| logSpeechRequest(req, "sending"); |
| setResponsePending(sdt); |
| |
| #ifdef GOT_PTHREADS |
| if (!asyncSignalEvent(sdt->requestEvent, req)) { |
| if (req) free(req); |
| setIntegerResponse(sdt, 0); |
| continue; |
| } |
| #else /* GOT_PTHREADS */ |
| handleSpeechRequest(sdt, req); |
| #endif /* GOT_PTHREADS */ |
| |
| break; |
| } |
| } |
| |
| static int |
| enqueueSpeechRequest (SpeechDriverThread *sdt, SpeechRequest *req) { |
| if (testThreadValidity(sdt)) { |
| logSpeechRequest(req, "enqueuing"); |
| |
| if (enqueueItem(sdt->requestQueue, req)) { |
| if (sdt->response.type != RSP_PENDING) { |
| if (getQueueSize(sdt->requestQueue) == 1) { |
| sendSpeechRequest(sdt); |
| } |
| } |
| |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static SpeechRequest * |
| newSpeechRequest (SpeechRequestType type, SpeechDatum *data) { |
| SpeechRequest *req; |
| size_t size = sizeof(*req) + getSpeechDataSize(data); |
| |
| if ((req = malloc(size))) { |
| memset(req, 0, sizeof(*req)); |
| req->type = type; |
| moveSpeechData(req->data, data); |
| return req; |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| int |
| speechRequest_sayText ( |
| SpeechDriverThread *sdt, |
| const char *text, size_t length, |
| size_t count, const unsigned char *attributes, |
| SayOptions options |
| ) { |
| SpeechRequest *req; |
| |
| BEGIN_SPEECH_DATA |
| {.address=text, .size=length+1}, |
| {.address=attributes, .size=count}, |
| END_SPEECH_DATA |
| |
| if ((req = newSpeechRequest(REQ_SAY_TEXT, data))) { |
| req->arguments.sayText.text = data[0].address; |
| req->arguments.sayText.length = length; |
| req->arguments.sayText.count = count; |
| req->arguments.sayText.attributes = data[1].address; |
| req->arguments.sayText.options = options; |
| |
| if (options & SAY_OPT_MUTE_FIRST) muteSpeechRequestQueue(sdt); |
| if (enqueueSpeechRequest(sdt, req)) return 1; |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechRequest_muteSpeech ( |
| SpeechDriverThread *sdt |
| ) { |
| SpeechRequest *req; |
| |
| if ((req = newSpeechRequest(REQ_MUTE_SPEECH, NULL))) { |
| muteSpeechRequestQueue(sdt); |
| if (enqueueSpeechRequest(sdt, req)) return 1; |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechRequest_drainSpeech ( |
| SpeechDriverThread *sdt |
| ) { |
| SpeechRequest *req; |
| |
| if ((req = newSpeechRequest(REQ_DRAIN_SPEECH, NULL))) { |
| if (enqueueSpeechRequest(sdt, req)) { |
| awaitSpeechResponse(sdt, SPEECH_RESPONSE_WAIT_TIMEOUT); |
| return 1; |
| } |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechRequest_setVolume ( |
| SpeechDriverThread *sdt, |
| unsigned char setting |
| ) { |
| SpeechRequest *req; |
| |
| if ((req = newSpeechRequest(REQ_SET_VOLUME, NULL))) { |
| req->arguments.setVolume.setting = setting; |
| if (enqueueSpeechRequest(sdt, req)) return 1; |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechRequest_setRate ( |
| SpeechDriverThread *sdt, |
| unsigned char setting |
| ) { |
| SpeechRequest *req; |
| |
| if ((req = newSpeechRequest(REQ_SET_RATE, NULL))) { |
| req->arguments.setRate.setting = setting; |
| if (enqueueSpeechRequest(sdt, req)) return 1; |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechRequest_setPitch ( |
| SpeechDriverThread *sdt, |
| unsigned char setting |
| ) { |
| SpeechRequest *req; |
| |
| if ((req = newSpeechRequest(REQ_SET_PITCH, NULL))) { |
| req->arguments.setPitch.setting = setting; |
| if (enqueueSpeechRequest(sdt, req)) return 1; |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| int |
| speechRequest_setPunctuation ( |
| SpeechDriverThread *sdt, |
| SpeechPunctuation setting |
| ) { |
| SpeechRequest *req; |
| |
| if ((req = newSpeechRequest(REQ_SET_PUNCTUATION, NULL))) { |
| req->arguments.setPunctuation.setting = setting; |
| if (enqueueSpeechRequest(sdt, req)) return 1; |
| |
| free(req); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| setThreadReady (SpeechDriverThread *sdt) { |
| setThreadState(sdt, THD_READY); |
| sendIntegerResponse(sdt, 1); |
| } |
| |
| static int |
| startSpeechDriver (SpeechDriverThread *sdt) { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "starting driver"); |
| return speech->construct(sdt->speechSynthesizer, sdt->driverParameters); |
| } |
| |
| static void |
| stopSpeechDriver (SpeechDriverThread *sdt) { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "stopping driver"); |
| speech->destruct(sdt->speechSynthesizer); |
| } |
| |
| #ifdef GOT_PTHREADS |
| ASYNC_CONDITION_TESTER(testSpeechDriverThreadStopping) { |
| SpeechDriverThread *sdt = data; |
| |
| return sdt->threadState == THD_STOPPING; |
| } |
| |
| ASYNC_EVENT_CALLBACK(handleSpeechMessageEvent) { |
| SpeechDriverThread *sdt = parameters->eventData; |
| SpeechMessage *msg = parameters->signalData; |
| |
| handleSpeechMessage(sdt, msg); |
| } |
| |
| ASYNC_EVENT_CALLBACK(handleSpeechRequestEvent) { |
| SpeechDriverThread *sdt = parameters->eventData; |
| SpeechRequest *req = parameters->signalData; |
| |
| handleSpeechRequest(sdt, req); |
| } |
| |
| static void |
| awaitSpeechDriverThreadTermination (SpeechDriverThread *sdt) { |
| void *result; |
| |
| pthread_join(sdt->threadIdentifier, &result); |
| } |
| |
| THREAD_FUNCTION(runSpeechDriverThread) { |
| SpeechDriverThread *sdt = argument; |
| |
| setThreadState(sdt, THD_STARTING); |
| |
| if ((sdt->requestEvent = asyncNewEvent(handleSpeechRequestEvent, sdt))) { |
| if (startSpeechDriver(sdt)) { |
| setThreadReady(sdt); |
| asyncWaitFor(testSpeechDriverThreadStopping, sdt); |
| stopSpeechDriver(sdt); |
| } else { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver construction failure"); |
| } |
| |
| asyncDiscardEvent(sdt->requestEvent); |
| sdt->requestEvent = NULL; |
| } else { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "request event construction failure"); |
| } |
| |
| { |
| int ok = sdt->threadState == THD_STOPPING; |
| |
| sendIntegerResponse(sdt, ok); |
| } |
| |
| setThreadState(sdt, THD_FINISHED); |
| return NULL; |
| } |
| #endif /* GOT_PTHREADS */ |
| |
| static void |
| deallocateSpeechRequest (void *item, void *data) { |
| SpeechRequest *req = item; |
| |
| logSpeechRequest(req, "unqueuing"); |
| free(req); |
| } |
| |
| int |
| constructSpeechDriverThread ( |
| SpeechSynthesizer *spk, |
| char **parameters |
| ) { |
| SpeechDriverThread *sdt; |
| |
| if ((sdt = malloc(sizeof(*sdt)))) { |
| memset(sdt, 0, sizeof(*sdt)); |
| setThreadState(sdt, THD_CONSTRUCTING); |
| setResponsePending(sdt); |
| |
| sdt->speechSynthesizer = spk; |
| sdt->driverParameters = parameters; |
| |
| if ((sdt->requestQueue = newQueue(deallocateSpeechRequest, NULL))) { |
| spk->driver.thread = sdt; |
| |
| #ifdef GOT_PTHREADS |
| if ((sdt->messageEvent = asyncNewEvent(handleSpeechMessageEvent, sdt))) { |
| pthread_t threadIdentifier; |
| int createError = createThread("speech-driver", |
| &threadIdentifier, NULL, |
| runSpeechDriverThread, sdt); |
| |
| if (!createError) { |
| sdt->threadIdentifier = threadIdentifier; |
| |
| if (awaitSpeechResponse(sdt, SPEECH_DRIVER_THREAD_START_TIMEOUT)) { |
| if (sdt->response.type == RSP_INTEGER) { |
| if (sdt->response.value.INTEGER) { |
| return 1; |
| } |
| } |
| |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread initialization failure"); |
| awaitSpeechDriverThreadTermination(sdt); |
| } else { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread initialization timeout"); |
| } |
| } else { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "driver thread creation failure: %s", strerror(createError)); |
| } |
| |
| asyncDiscardEvent(sdt->messageEvent); |
| sdt->messageEvent = NULL; |
| } else { |
| logMessage(LOG_CATEGORY(SPEECH_EVENTS), "response event construction failure"); |
| } |
| #else /* GOT_PTHREADS */ |
| if (startSpeechDriver(sdt)) { |
| setThreadReady(sdt); |
| return 1; |
| } |
| #endif /* GOT_PTHREADS */ |
| |
| spk->driver.thread = NULL; |
| deallocateQueue(sdt->requestQueue); |
| } |
| |
| free(sdt); |
| } else { |
| logMallocError(); |
| } |
| |
| spk->driver.thread = NULL; |
| return 0; |
| } |
| |
| void |
| destroySpeechDriverThread (SpeechSynthesizer *spk) { |
| SpeechDriverThread *sdt = spk->driver.thread; |
| |
| deleteElements(sdt->requestQueue); |
| |
| #ifdef GOT_PTHREADS |
| if (enqueueSpeechRequest(sdt, NULL)) { |
| sdt->isBeingDestroyed = 1; |
| awaitSpeechResponse(sdt, SPEECH_DRIVER_THREAD_STOP_TIMEOUT); |
| |
| setResponsePending(sdt); |
| awaitSpeechResponse(sdt, SPEECH_DRIVER_THREAD_STOP_TIMEOUT); |
| |
| awaitSpeechDriverThreadTermination(sdt); |
| } |
| |
| if (sdt->messageEvent) asyncDiscardEvent(sdt->messageEvent); |
| #else /* GOT_PTHREADS */ |
| stopSpeechDriver(sdt); |
| setThreadState(sdt, THD_FINISHED); |
| #endif /* GOT_PTHREADS */ |
| |
| sdt->speechSynthesizer->driver.thread = NULL; |
| deallocateQueue(sdt->requestQueue); |
| free(sdt); |
| } |
| #endif /* ENABLE_SPEECH_SUPPORT */ |