blob: 9d3f6f97399ca9a4d719a8954d9b0b8824dcc216 [file] [log] [blame] [edit]
/*
* 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);
}