blob: 563edf7f4690ed38deaec128b3483abeaea107d3 [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>.
*/
/* BRLTTY speech driver for the Festival text to speech engine.
* Written by: Nikhil Nair <nn201@cus.cam.ac.uk>
* Maintained by: Dave Mielke <dave@mielke.cc>
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include "log.h"
typedef enum {
PARM_COMMAND,
PARM_NAME
} DriverParameter;
#define SPKPARMS "command", "name"
#include "spk_driver.h"
#include "speech.h" /* for speech definitions */
static char **festivalParameters = NULL;
static FILE *festivalStream = NULL;
static float festivalRate;
static int writeCommand (const char *command, int reopen);
static int
setRate (float setting, int reopen) {
char command[0X40];
snprintf(command, sizeof(command), "(Parameter.set 'Duration_Stretch %f)", 1.0/setting);
return writeCommand(command, reopen);
}
static void
closeStream (void) {
logMessage(LOG_DEBUG, "stopping festival");
pclose(festivalStream);
festivalStream = NULL;
}
static int
openStream (void) {
const char *command = festivalParameters[PARM_COMMAND];
if (!command || !*command) command = "festival";
logMessage(LOG_DEBUG, "starting festival: command=%s", command);
if ((festivalStream = popen(command, "w"))) {
setvbuf(festivalStream, NULL, _IOLBF, 0X1000);
if (!writeCommand("(gc-status nil)", 0)) return 0;
if (!writeCommand("(audio_mode 'async)", 0)) return 0;
if (!writeCommand("(Parameter.set 'Audio_Method 'netaudio)", 0)) return 0;
{
const char *name = festivalParameters[PARM_NAME];
if (name && *name) {
if (strcasecmp(name, "Kevin") == 0) {
if (!writeCommand("(voice_ked_diphone)", 0)) return 0;
} else if (strcasecmp(name, "Kal") == 0) {
if (!writeCommand("(voice_kal_diphone)", 0)) return 0;
} else {
logMessage(LOG_WARNING, "unknown Festival voice name: %s", name);
}
}
}
if (festivalRate != 0.0)
if (!setRate(festivalRate, 0))
return 0;
return 1;
}
return 0;
}
static int
writeString (const char *string, int reopen) {
if (!festivalStream) {
if (!reopen) return 0;
if (!openStream()) return 0;
}
fputs(string, festivalStream);
if (!ferror(festivalStream)) return 1;
logSystemError("fputs");
closeStream();
return 0;
}
static int
writeCommand (const char *command, int reopen) {
return writeString(command, reopen) && writeString("\n", 0);
}
static void
spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
setRate(festivalRate=getFloatSpeechRate(setting), 1);
}
static int
spk_construct (SpeechSynthesizer *spk, char **parameters) {
spk->setRate = spk_setRate;
festivalParameters = parameters;
festivalRate = 0.0;
return openStream();
}
static void
spk_destruct (SpeechSynthesizer *spk) {
if (writeCommand("(quit)", 0)) closeStream();
festivalParameters = NULL;
}
static void
spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
int started = 0;
unsigned int index;
for (index=0; index<length; index+=1) {
unsigned char byte = buffer[index];
if (iscntrl(byte)) byte = ' ';
if (!isspace(byte)) {
if (!started) {
if (!writeString("(SayText \"", 1)) return;
started = 1;
}
}
if (started) {
const char bytes[] = {'\\', byte, 0};
const char *string = bytes;
if (!strchr("\\\"", byte)) string += 1;
if (!writeString(string, 0)) return;
}
}
if (started) {
if (!writeString("\")\n", 0)) return;
}
}
static void
spk_mute (SpeechSynthesizer *spk) {
writeCommand("(audio_mode 'shutup)", 0);
}