| /* |
| * 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 "log.h" |
| #include "parse.h" |
| |
| typedef enum { |
| PARM_PORT, |
| PARM_MODULE, |
| PARM_LANGUAGE, |
| PARM_VOICE, |
| PARM_NAME |
| } DriverParameter; |
| #define SPKPARMS "port", "module", "language", "voice", "name" |
| |
| #include "spk_driver.h" |
| |
| #include <libspeechd.h> |
| |
| static SPDConnection *connectionHandle = NULL; |
| static const char *moduleName; |
| static const char *languageName; |
| static SPDVoiceType voiceType; |
| static const char *voiceName; |
| static signed int relativeVolume; |
| static signed int relativeRate; |
| static signed int relativePitch; |
| static SPDPunctuation punctuationVerbosity; |
| |
| static void |
| clearSettings (void) { |
| moduleName = NULL; |
| languageName = NULL; |
| voiceType = -1; |
| voiceName = NULL; |
| relativeVolume = 0; |
| relativeRate = 0; |
| relativePitch = 0; |
| punctuationVerbosity = -1; |
| } |
| |
| static void |
| closeConnection (void) { |
| if (connectionHandle) { |
| spd_close(connectionHandle); |
| connectionHandle = NULL; |
| } |
| } |
| |
| typedef void (*SpeechdAction) (const void *data); |
| |
| static void |
| speechdAction (SpeechdAction action, const void *data) { |
| if (connectionHandle) { |
| action(data); |
| if (!connectionHandle->stream) closeConnection(); |
| } |
| } |
| |
| static void |
| setModule (const void *data) { |
| if (moduleName) spd_set_output_module(connectionHandle, moduleName); |
| } |
| |
| static void |
| setLanguage (const void *data) { |
| if (languageName) spd_set_language(connectionHandle, languageName); |
| } |
| |
| static void |
| setVoiceType (const void *data) { |
| if (voiceType != -1) spd_set_voice_type(connectionHandle, voiceType); |
| } |
| |
| static void |
| setVoiceName (const void *data) { |
| if (voiceName) spd_set_synthesis_voice(connectionHandle, voiceName); |
| } |
| |
| static void |
| setVolume (const void *data) { |
| spd_set_volume(connectionHandle, relativeVolume); |
| } |
| |
| static void |
| spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) { |
| relativeVolume = getIntegerSpeechVolume(setting, 100) - 100; |
| speechdAction(setVolume, NULL); |
| logMessage(LOG_DEBUG, "set volume: %u -> %d", setting, relativeVolume); |
| } |
| |
| static void |
| setRate (const void *data) { |
| spd_set_voice_rate(connectionHandle, relativeRate); |
| } |
| |
| static void |
| spk_setRate (SpeechSynthesizer *spk, unsigned char setting) { |
| relativeRate = getIntegerSpeechRate(setting, 100) - 100; |
| speechdAction(setRate, NULL); |
| logMessage(LOG_DEBUG, "set rate: %u -> %d", setting, relativeRate); |
| } |
| |
| static void |
| setPitch (const void *data) { |
| spd_set_voice_pitch(connectionHandle, relativePitch); |
| } |
| |
| static void |
| spk_setPitch (SpeechSynthesizer *spk, unsigned char setting) { |
| relativePitch = getIntegerSpeechPitch(setting, 100) - 100; |
| speechdAction(setPitch, NULL); |
| logMessage(LOG_DEBUG, "set pitch: %u -> %d", setting, relativePitch); |
| } |
| |
| static void |
| setPunctuation (const void *data) { |
| if (punctuationVerbosity != -1) spd_set_punctuation(connectionHandle, punctuationVerbosity); |
| } |
| |
| static void |
| spk_setPunctuation (SpeechSynthesizer *spk, SpeechPunctuation setting) { |
| punctuationVerbosity = (setting <= SPK_PUNCTUATION_NONE)? SPD_PUNCT_NONE: |
| (setting >= SPK_PUNCTUATION_ALL)? SPD_PUNCT_ALL: |
| SPD_PUNCT_SOME; |
| speechdAction(setPunctuation, NULL); |
| logMessage(LOG_DEBUG, "set punctuation: %u -> %d", setting, punctuationVerbosity); |
| } |
| |
| static void |
| cancelSpeech (const void *data) { |
| spd_cancel(connectionHandle); |
| } |
| |
| static int |
| openConnection (void) { |
| if (!connectionHandle) { |
| if (!(connectionHandle = spd_open("brltty", "main", NULL, SPD_MODE_THREADED))) { |
| logMessage(LOG_ERR, "speech dispatcher open failure"); |
| return 0; |
| } |
| |
| { |
| static const SpeechdAction actions[] = { |
| setModule, |
| setLanguage, |
| setVoiceType, |
| setVoiceName, |
| setVolume, |
| setRate, |
| setPitch, |
| setPunctuation, |
| NULL |
| }; |
| const SpeechdAction *action = actions; |
| while (*action) speechdAction(*action++, NULL); |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| spk_construct (SpeechSynthesizer *spk, char **parameters) { |
| spk->setVolume = spk_setVolume; |
| spk->setRate = spk_setRate; |
| spk->setPitch = spk_setPitch; |
| spk->setPunctuation = spk_setPunctuation; |
| |
| clearSettings(); |
| |
| if (parameters[PARM_PORT] && *parameters[PARM_PORT]) { |
| static const int minimumPort = 0X1; |
| static const int maximumPort = 0XFFFF; |
| int port = 0; |
| |
| if (validateInteger(&port, parameters[PARM_PORT], &minimumPort, &maximumPort)) { |
| char number[0X10]; |
| snprintf(number, sizeof(number), "%d", port); |
| setenv("SPEECHD_PORT", number, 1); |
| } else { |
| logMessage(LOG_WARNING, "%s: %s", "invalid port number", parameters[PARM_PORT]); |
| } |
| } |
| |
| if (parameters[PARM_MODULE] && *parameters[PARM_MODULE]) { |
| moduleName = parameters[PARM_MODULE]; |
| } |
| |
| if (parameters[PARM_LANGUAGE] && *parameters[PARM_LANGUAGE]) { |
| languageName = parameters[PARM_LANGUAGE]; |
| } |
| |
| if (parameters[PARM_VOICE] && *parameters[PARM_VOICE]) { |
| static const SPDVoiceType voices[] = { |
| SPD_MALE1, SPD_FEMALE1, |
| SPD_MALE2, SPD_FEMALE2, |
| SPD_MALE3, SPD_FEMALE3, |
| SPD_CHILD_MALE, SPD_CHILD_FEMALE |
| }; |
| |
| static const char *choices[] = { |
| "male1", "female1", |
| "male2", "female2", |
| "male3", "female3", |
| "child_male", "child_female", |
| NULL |
| }; |
| |
| unsigned int choice = 0; |
| |
| if (validateChoice(&choice, parameters[PARM_VOICE], choices)) { |
| voiceType = voices[choice]; |
| } else { |
| logMessage(LOG_WARNING, "%s: %s", "invalid voice type", parameters[PARM_VOICE]); |
| } |
| } |
| |
| if (parameters[PARM_NAME] && *parameters[PARM_NAME]) { |
| voiceName = parameters[PARM_NAME]; |
| } |
| |
| return openConnection(); |
| } |
| |
| static void |
| spk_destruct (SpeechSynthesizer *spk) { |
| closeConnection(); |
| clearSettings(); |
| } |
| |
| typedef struct { |
| const unsigned char *text; |
| size_t length; |
| size_t count; |
| SPDPriority priority; |
| } SayData; |
| |
| static void |
| sayText (const void *data) { |
| const SayData *say = data; |
| |
| if (say->count == 1) { |
| char string[say->length + 1]; |
| memcpy(string, say->text, say->length); |
| string[say->length] = 0; |
| spd_char(connectionHandle, say->priority, string); |
| } else { |
| spd_sayf(connectionHandle, say->priority, "%.*s", (int)say->length, say->text); |
| } |
| } |
| |
| static void |
| spk_say (SpeechSynthesizer *spk, const unsigned char *text, size_t length, size_t count, const unsigned char *attributes) { |
| const SayData say = { |
| .text = text, |
| .length = length, |
| .count = count, |
| .priority = SPD_MESSAGE |
| }; |
| |
| speechdAction(sayText, &say); |
| if (!connectionHandle) { |
| if (openConnection()) { |
| speechdAction(sayText, &say); |
| } |
| } |
| } |
| |
| static void |
| spk_mute (SpeechSynthesizer *spk) { |
| speechdAction(cancelSpeech, NULL); |
| } |