blob: 06f071136f8ef6d9eed35a49074a03aeeedcdfa9 [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 <errno.h>
#include <ctype.h>
#include <eci.h>
#ifdef HAVE_ICONV_H
#include <iconv.h>
#define ICONV_NULL ((iconv_t)-1)
#else /* HAVE_ICONV_H */
#warning iconv is not available
#endif /* HAVE_ICONV_H */
#include "log.h"
#include "parse.h"
typedef enum {
PARM_Quality,
PARM_Mode,
PARM_Synthesize,
PARM_Abbreviations,
PARM_Years,
PARM_Language,
PARM_Voice,
PARM_Gender,
PARM_HeadSize,
PARM_PitchBaseline,
PARM_Expressiveness,
PARM_Roughness,
PARM_Breathiness,
PARM_Volume,
PARM_Speed
} DriverParameter;
#define SPKPARMS "Quality", "Mode", "Synthesize", "Abbreviations", "Years", "Language", "Voice", "Gender", "HeadSize", "PitchBaseline", "Expressiveness", "Roughness", "Breathiness", "Volume", "Speed"
#include "spk_driver.h"
#define MAXIMUM_SAMPLES 0X800
typedef void SaveSettingFunction (SpeechSynthesizer *spk, int setting);
typedef struct {
const char *name; // must be first
int rate;
} QualityChoice;
static const QualityChoice qualityChoices[] = {
{ .name = "poor",
.rate = 8000
},
{ .name = "fair",
.rate = 11025
},
{ .name = "good",
.rate = 22050
},
{ .name = NULL }
};
static const char *abbreviationsChoices[] = {"on", "off", NULL};
static const char *yearsChoices[] = {"off", "on", NULL};
static const char *synthesizeChoices[] = {"sentences", "all", NULL};
static const char *modeChoices[] = {"words", "letters", "punctuation", "phonetic", NULL};
static const char *genderChoices[] = {"male", "female", NULL};
typedef struct {
const char *name; // must be first
const char *language;
const char *territory;
const char *encoding;
int identifier;
} LanguageChoice;
static const LanguageChoice languageChoices[] = {
#include "languages.h"
{ .name = NULL }
};
typedef enum {
VOICE_TYPE_CURRENT,
VOICE_TYPE_MAN1,
VOICE_TYPE_WOMAN1,
VOICE_TYPE_CHILD1,
VOICE_TYPE_MAN2,
VOICE_TYPE_MAN3,
VOICE_TYPE_WOMAN2,
VOICE_TYPE_MATRIARCH1,
VOICE_TYPE_PATRIARCH1,
VOICE_TYPE_USER1,
VOICE_TYPE_USER2,
VOICE_TYPE_USER3,
VOICE_TYPE_USER4,
VOICE_TYPE_USER5,
VOICE_TYPE_USER6,
VOICE_TYPE_USER7
} VoiceType;
typedef struct {
const char *name; // must be first
int language;
VoiceType type;
} VoiceChoice;
#define GENERIC_VOICE_LANGUAGE NODEFINEDCODESET
#define GENERIC_VOICE(t,n) { .name=#n, .language=GENERIC_VOICE_LANGUAGE, .type=VOICE_TYPE_##t }
static const VoiceChoice voiceChoices[] = {
GENERIC_VOICE(MAN1, man),
GENERIC_VOICE(WOMAN1, woman),
GENERIC_VOICE(CHILD1, child),
GENERIC_VOICE(PATRIARCH1, patriarch),
GENERIC_VOICE(MATRIARCH1, matriarch),
#include "voices.h"
{ .name = NULL }
};
static int
isGenericVoice (const VoiceChoice *voice) {
return voice->language == GENERIC_VOICE_LANGUAGE;
}
struct SpeechDataStruct {
struct {
uint32_t binary;
char text[20];
} version;
struct {
ECIHand handle;
unsigned useSSML:1;
} eci;
struct {
const LanguageChoice *language;
const VoiceChoice *voice;
} setting;
struct {
short *buffer;
FILE *stream;
char *command;
} pcm;
struct {
char *buffer;
size_t size;
} say;
struct {
int unitType;
int inputType;
} current;
#ifdef ICONV_NULL
struct {
iconv_t handle;
} iconv;
#endif /* ICONV_NULL */
};
static void
saveLanguageSetting (SpeechSynthesizer *spk, int setting) {
spk->driver.data->setting.language = &languageChoices[setting];
}
static void
saveVoiceSetting (SpeechSynthesizer *spk, int setting) {
spk->driver.data->setting.voice = &voiceChoices[setting];
}
static void
reportError (SpeechSynthesizer *spk, const char *routine) {
int status = eciProgStatus(spk->driver.data->eci.handle);
char message[100];
eciErrorMessage(spk->driver.data->eci.handle, message);
logMessage(LOG_ERR, "%s error %4.4X: %s", routine, status, message);
}
static void
reportSetting (const char *type, const char *description, const char *setting, const char *unit) {
if (!unit) unit = "";
logMessage(LOG_DEBUG, "%s setting: %s = %s%s", type, description, setting, unit);
}
static void
reportParameter (const char *type, const char *description, int setting, const void *choices, size_t size, const char *unit) {
char buffer[0X10];
const char *value = buffer;
if (setting == -1) {
value = "unknown";
} else if (choices) {
const void *choice = choices;
while (1) {
typedef struct {
const char *name;
} Entry;
const Entry *entry = choice;
const char *name = entry->name;
if (!name) break;
int index = (choice - choices) / size;
if (setting == index) {
value = name;
break;
}
choice += size;
}
}
if (value == buffer) snprintf(buffer, sizeof(buffer), "%d", setting);
reportSetting(type, description, value, unit);
}
static int
getEnvironmentParameter (SpeechSynthesizer *spk, enum ECIParam parameter) {
return eciGetParam(spk->driver.data->eci.handle, parameter);
}
static const char *
getEnvironmentParameterUnit (enum ECIParam parameter) {
switch (parameter) {
default:
return "";
}
}
static void
reportEnvironmentParameter (SpeechSynthesizer *spk, const char *description, enum ECIParam parameter, const void *choices, size_t size) {
int setting = getEnvironmentParameter(spk, parameter);
reportParameter("environment", description, setting, choices, size, getEnvironmentParameterUnit(parameter));
}
static int
setEnvironmentParameter (SpeechSynthesizer *spk, const char *description, enum ECIParam parameter, int setting) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "set environment parameter: %s: %d=%d", description, parameter, setting);
return eciSetParam(spk->driver.data->eci.handle, parameter, setting) >= 0;
}
static int
choiceEnvironmentParameter (SpeechSynthesizer *spk, const char *description, const char *value, enum ECIParam parameter, const void *choices, size_t size, SaveSettingFunction *save) {
int ok = !*value;
if (!ok) {
unsigned int setting;
if (validateChoiceEx(&setting, value, choices, size)) {
if (save) {
save(spk, setting);
ok = 1;
} else if (setEnvironmentParameter(spk, description, parameter, setting)) {
ok = 1;
} else {
logMessage(LOG_WARNING, "%s not supported: %s", description, value);
}
} else {
logMessage(LOG_WARNING, "invalid %s setting: %s", description, value);
}
}
if (!save) reportEnvironmentParameter(spk, description, parameter, choices, size);
return ok;
}
static int
setUnitType (SpeechSynthesizer *spk, int newUnits) {
if (newUnits != spk->driver.data->current.unitType) {
if (!setEnvironmentParameter(spk, "real world units", eciRealWorldUnits, newUnits)) return 0;
spk->driver.data->current.unitType = newUnits;
}
return 1;
}
static int
useInternalUnits (SpeechSynthesizer *spk) {
return setUnitType(spk, 0);
}
static int
useExternalUnits (SpeechSynthesizer *spk) {
return setUnitType(spk, 1);
}
static int
useParameterUnit (SpeechSynthesizer *spk, enum ECIVoiceParam parameter) {
switch (parameter) {
case eciVolume:
if (!useInternalUnits(spk)) return 0;
break;
case eciPitchBaseline:
case eciSpeed:
if (!useExternalUnits(spk)) return 0;
break;
default:
break;
}
return 1;
}
static int
getVoiceParameter (SpeechSynthesizer *spk, enum ECIVoiceParam parameter) {
if (!useParameterUnit(spk, parameter)) return 0;
return eciGetVoiceParam(spk->driver.data->eci.handle, VOICE_TYPE_CURRENT, parameter);
}
static const char *
getVoiceParameterUnit (enum ECIVoiceParam parameter) {
switch (parameter) {
case eciVolume:
return "%";
case eciPitchBaseline:
return "Hz";
case eciSpeed:
return "wpm";
default:
return "";
}
}
static void
reportVoiceParameter (SpeechSynthesizer *spk, const char *description, enum ECIVoiceParam parameter, const char *const *choices) {
reportParameter("voice", description, getVoiceParameter(spk, parameter), choices, sizeof(*choices), getVoiceParameterUnit(parameter));
}
static int
setVoiceParameter (SpeechSynthesizer *spk, const char *description, enum ECIVoiceParam parameter, int setting) {
if (!useParameterUnit(spk, parameter)) return 0;
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "set voice parameter: %s: %d=%d%s", description, parameter, setting, getVoiceParameterUnit(parameter));
return eciSetVoiceParam(spk->driver.data->eci.handle, VOICE_TYPE_CURRENT, parameter, setting) >= 0;
}
static int
choiceVoiceParameter (SpeechSynthesizer *spk, const char *description, const char *value, enum ECIVoiceParam parameter, const char *const *choices) {
int ok = !*value;
if (!ok) {
unsigned int setting;
if (validateChoice(&setting, value, choices)) {
if (setVoiceParameter(spk, description, parameter, setting)) {
ok = 1;
} else {
logMessage(LOG_WARNING, "%s not supported: %s", description, value);
}
} else {
logMessage(LOG_WARNING, "invalid %s setting: %s", description, value);
}
}
reportVoiceParameter(spk, description, parameter, choices);
return ok;
}
static int
rangeVoiceParameter (SpeechSynthesizer *spk, const char *description, const char *value, enum ECIVoiceParam parameter, int minimum, int maximum) {
int ok = 0;
if (*value) {
int setting;
if (validateInteger(&setting, value, &minimum, &maximum)) {
if (setVoiceParameter(spk, description, parameter, setting)) {
ok = 1;
}
} else {
logMessage(LOG_WARNING, "invalid %s setting: %s", description, value);
}
}
reportVoiceParameter(spk, description, parameter, NULL);
return ok;
}
static void
spk_setVolume (SpeechSynthesizer *spk, unsigned char setting) {
setVoiceParameter(spk, "volume", eciVolume, getIntegerSpeechVolume(setting, 100));
}
static void
spk_setRate (SpeechSynthesizer *spk, unsigned char setting) {
setVoiceParameter(spk, "rate", eciSpeed, (int)(getFloatSpeechRate(setting) * 210.0));
}
static const LanguageChoice *
findLanguage (int identifier) {
const LanguageChoice *choice = languageChoices;
while (choice->name) {
if (choice->identifier == identifier) return choice;
choice += 1;
}
logMessage(LOG_WARNING, "language identifier not defined: 0X%08X", identifier);
return NULL;
}
static const LanguageChoice *
getLanguage (SpeechSynthesizer *spk) {
int identifier = getEnvironmentParameter(spk, eciLanguageDialect);
return findLanguage(identifier);
}
static void
stopSpeech (SpeechSynthesizer *spk) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "stop");
if (eciStop(spk->driver.data->eci.handle)) {
} else {
reportError(spk, "eciStop");
}
}
static void
spk_mute (SpeechSynthesizer *spk) {
stopSpeech(spk);
}
static int
pcmMakeCommand (SpeechSynthesizer *spk) {
int rate = getEnvironmentParameter(spk, eciSampleRate);
char buffer[0X100];
snprintf(
buffer, sizeof(buffer),
"sox -q -t raw -c 1 -b %" PRIsize " -e signed-integer -r %d - -d",
(sizeof(*spk->driver.data->pcm.buffer) * 8), qualityChoices[rate].rate
);
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "PCM command: %s", buffer);
spk->driver.data->pcm.command = strdup(buffer);
if (spk->driver.data->pcm.command) return 1;
logMallocError();
return 0;
}
static int
pcmOpenStream (SpeechSynthesizer *spk) {
if (!spk->driver.data->pcm.stream) {
if (!spk->driver.data->pcm.command) {
if (!pcmMakeCommand(spk)) {
return 0;
}
}
if (!(spk->driver.data->pcm.stream = popen(spk->driver.data->pcm.command, "w"))) {
logMessage(LOG_WARNING, "can't start command: %s", strerror(errno));
return 0;
}
setvbuf(spk->driver.data->pcm.stream, NULL, _IONBF, 0);
}
return 1;
}
static void
pcmCloseStream (SpeechSynthesizer *spk) {
if (spk->driver.data->pcm.stream) {
pclose(spk->driver.data->pcm.stream);
spk->driver.data->pcm.stream = NULL;
}
}
static enum ECICallbackReturn
clientCallback (ECIHand eci, enum ECIMessage message, long parameter, void *data) {
SpeechSynthesizer *spk = data;
switch (message) {
case eciWaveformBuffer:
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "write samples: %ld", parameter);
fwrite(spk->driver.data->pcm.buffer, sizeof(*spk->driver.data->pcm.buffer), parameter, spk->driver.data->pcm.stream);
if (ferror(spk->driver.data->pcm.stream)) return eciDataAbort;
break;
case eciIndexReply:
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "index reply: %ld", parameter);
tellSpeechLocation(spk, parameter);
break;
default:
break;
}
return eciDataProcessed;
}
static int
addText (SpeechSynthesizer *spk, const char *text) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "add text: \"%s\"", text);
if (eciAddText(spk->driver.data->eci.handle, text)) return 1;
reportError(spk, "eciAddText");
return 0;
}
static int
insertIndex (SpeechSynthesizer *spk, int index) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "insert index: %d", index);
if (eciInsertIndex(spk->driver.data->eci.handle, index)) return 1;
reportError(spk, "eciInsertIndex");
return 0;
}
static int
setInputType (SpeechSynthesizer *spk, int newInputType) {
if (newInputType != spk->driver.data->current.inputType) {
if (!setEnvironmentParameter(spk, "input type", eciInputType, newInputType)) return 0;
spk->driver.data->current.inputType = newInputType;
}
return 1;
}
static int
disableAnnotations (SpeechSynthesizer *spk) {
return setInputType(spk, 0);
}
static int
enableAnnotations (SpeechSynthesizer *spk) {
return setInputType(spk, 1);
}
static int
writeAnnotation (SpeechSynthesizer *spk, const char *annotation) {
if (!enableAnnotations(spk)) return 0;
char text[0X100];
snprintf(text, sizeof(text), " `%s ", annotation);
return addText(spk, text);
}
#ifdef ICONV_NULL
static int
prepareTextConversion (SpeechSynthesizer *spk) {
spk->driver.data->iconv.handle = ICONV_NULL;
const LanguageChoice *choice = getLanguage(spk);
if (!choice) return 0;
iconv_t *handle = iconv_open(choice->encoding, "UTF-8");
if (handle == ICONV_NULL) {
logMessage(LOG_WARNING, "character encoding not supported: %s: %s", choice->encoding, strerror(errno));
return 0;
}
spk->driver.data->iconv.handle = handle;
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "using character encoding: %s", choice->encoding);
return 1;
}
#endif /* ICONV_NULL */
static int
ensureSayBufferSize (SpeechSynthesizer *spk, size_t size) {
if (size > spk->driver.data->say.size) {
size |= 0XFF;
size += 1;
char *newBuffer = malloc(size);
if (!newBuffer) {
logSystemError("speech buffer allocation");
return 0;
}
if (spk->driver.data->say.buffer) free(spk->driver.data->say.buffer);
spk->driver.data->say.buffer = newBuffer;
spk->driver.data->say.size = size;
}
return 1;
}
static int
addCharacters (SpeechSynthesizer *spk, const unsigned char *buffer, int from, int to) {
int length = to - from;
if (!length) return 1;
if (!ensureSayBufferSize(spk, length+1)) return 0;
memcpy(spk->driver.data->say.buffer, &buffer[from], length);
spk->driver.data->say.buffer[length] = 0;
return addText(spk, spk->driver.data->say.buffer);
}
static int
addSegment (SpeechSynthesizer *spk, const unsigned char *buffer, int from, int to, const int *indexMap) {
if (spk->driver.data->eci.useSSML) {
for (int index=from; index<to; index+=1) {
const char *entity = NULL;
switch (buffer[index]) {
case '<':
entity = "lt";
break;
case '>':
entity = "gt";
break;
case '&':
entity = "amp";
break;
case '"':
entity = "quot";
break;
case '\'':
entity = "apos";
break;
default:
continue;
}
if (!addCharacters(spk, buffer, from, index)) return 0;
from = index + 1;
size_t length = strlen(entity);
char text[1 + length + 2];
snprintf(text, sizeof(text), "&%s;", entity);
if (!addText(spk, text)) return 0;
}
if (!addCharacters(spk, buffer, from, to)) return 0;
} else {
#ifdef ICONV_NULL
char *inputStart = (char *)&buffer[from];
size_t inputLeft = to - from;
size_t outputLeft = inputLeft * 10;
char outputBuffer[outputLeft];
char *outputStart = outputBuffer;
int result = iconv(spk->driver.data->iconv.handle, &inputStart, &inputLeft, &outputStart, &outputLeft);
if (result == -1) {
logSystemError("iconv");
return 0;
}
if (!addCharacters(spk, (unsigned char *)outputBuffer, 0, (outputStart - outputBuffer))) return 0;
#else /* ICONV_NULL */
if (!addCharacters(spk, buffer, from, to)) return 0;
#endif /* ICONV_NULL */
}
return insertIndex(spk, indexMap[to]);
}
static int
addSegments (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, const int *indexMap) {
if (spk->driver.data->eci.useSSML && !addText(spk, "<speak>")) return 0;
int wasSpace = 1;
int from = 0;
int to;
for (to=0; to<length; to+=1) {
int isSpace = isspace(buffer[to])? 1: 0;
if (isSpace != wasSpace) {
wasSpace = isSpace;
if (to > from) {
if (!addSegment(spk, buffer, from, to, indexMap)) return 0;
from = to;
}
}
}
if (!addSegment(spk, buffer, from, to, indexMap)) return 0;
if (spk->driver.data->eci.useSSML && !addText(spk, "</speak>")) return 0;
return 1;
}
static void
spk_say (SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes) {
int ok = 0;
if (pcmOpenStream(spk)) {
int indexMap[length + 1];
{
int from = 0;
int to = 0;
while (from < length) {
char character = buffer[from];
indexMap[from++] = ((character & 0X80) && !(character & 0X40))? -1: to++;
}
indexMap[from] = to;
}
if (addSegments(spk, buffer, length, indexMap)) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "synthesize");
if (eciSynthesize(spk->driver.data->eci.handle)) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "synchronize");
if (eciSynchronize(spk->driver.data->eci.handle)) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "finished");
tellSpeechFinished(spk);
ok = 1;
} else {
reportError(spk, "eciSynchronize");
}
} else {
reportError(spk, "eciSynthesize");
}
}
if (!ok) stopSpeech(spk);
pcmCloseStream(spk);
}
}
static void
setLanguageAndVoice (SpeechSynthesizer *spk) {
const LanguageChoice *language = spk->driver.data->setting.language;
const VoiceChoice *voice = spk->driver.data->setting.voice;
if (voice && !isGenericVoice(voice)) {
if (!language) {
language = findLanguage(voice->language);
} else if (language->identifier != voice->language) {
logMessage(LOG_WARNING, "voice %s is incompatible with language %s", voice->name, language->name);
}
}
if (language) {
if (!setEnvironmentParameter(spk, "language", eciLanguageDialect, language->identifier)) {
logMessage(LOG_WARNING, "language not supported: %s", language->name);
}
}
language = getLanguage(spk);
reportSetting("environment", "language", language->name, NULL);
if (voice) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "copy voice: %d (%s)", voice->type, voice->name);
if (eciCopyVoice(spk->driver.data->eci.handle, voice->type, VOICE_TYPE_CURRENT)) {
const char *description = isGenericVoice(voice)? "type": "name";
reportSetting("voice", description, voice->name, NULL);
} else {
reportError(spk, "eciCopyVoice");
}
}
}
static void
setParameters (SpeechSynthesizer *spk, char **parameters) {
choiceEnvironmentParameter(spk, "quality (sample rate)", parameters[PARM_Quality], eciSampleRate, qualityChoices, sizeof(*qualityChoices), NULL);
choiceEnvironmentParameter(spk, "mode (text mode)", parameters[PARM_Mode], eciTextMode, modeChoices, sizeof(*modeChoices), NULL);
choiceEnvironmentParameter(spk, "synthesize (synth mode)", parameters[PARM_Synthesize], eciSynthMode, synthesizeChoices, sizeof(*synthesizeChoices), NULL);
choiceEnvironmentParameter(spk, "abbreviations (dictionary)", parameters[PARM_Abbreviations], eciDictionary, abbreviationsChoices, sizeof(*abbreviationsChoices), NULL);
choiceEnvironmentParameter(spk, "years (number mode)", parameters[PARM_Years], eciNumberMode, yearsChoices, sizeof(*yearsChoices), NULL);
choiceEnvironmentParameter(spk, "language", parameters[PARM_Language], eciLanguageDialect, languageChoices, sizeof(*languageChoices), saveLanguageSetting);
choiceEnvironmentParameter(spk, "voice name", parameters[PARM_Voice], eciNumParams, voiceChoices, sizeof(*voiceChoices), saveVoiceSetting);
setLanguageAndVoice(spk);
choiceVoiceParameter(spk, "gender", parameters[PARM_Gender], eciGender, genderChoices);
rangeVoiceParameter(spk, "head size", parameters[PARM_HeadSize], eciHeadSize, 0, 100);
rangeVoiceParameter(spk, "pitch baseline", parameters[PARM_PitchBaseline], eciPitchBaseline, 40, 422);
rangeVoiceParameter(spk, "expressiveness (pitch fluctuation)", parameters[PARM_Expressiveness], eciPitchFluctuation, 0, 100);
rangeVoiceParameter(spk, "roughness", parameters[PARM_Roughness], eciRoughness, 0, 100);
rangeVoiceParameter(spk, "breathiness", parameters[PARM_Breathiness], eciBreathiness, 0, 100);
rangeVoiceParameter(spk, "volume", parameters[PARM_Volume], eciVolume, 0, 100);
rangeVoiceParameter(spk, "speed", parameters[PARM_Speed], eciSpeed, 70, 1297);
#ifdef ICONV_NULL
spk->driver.data->eci.useSSML = !prepareTextConversion(spk);
#else /* ICONV_NULL */
spk->driver.data->eci.useSSML = 1;
#endif /* ICONV_NULL */
if (spk->driver.data->eci.useSSML) {
logMessage(LOG_CATEGORY(SPEECH_DRIVER), "using SSML");
}
}
static void
writeAnnotations (SpeechSynthesizer *spk) {
if (spk->driver.data->eci.useSSML) {
writeAnnotation(spk, "gfa1"); // enable SSML
writeAnnotation(spk, "gfa2");
}
disableAnnotations(spk);
}
static uint32_t
parseVersion (const char *text) {
uint32_t result = 0;
const unsigned int width = 8;
unsigned int shift = sizeof(result) * 8 / width * width;
while (*text && shift) {
char *end;
long digit = strtol(text, &end, 10);
shift -= width;
result |= digit << shift;
text = end;
if (*text) text += 1;
}
return result;
}
static int
spk_construct (SpeechSynthesizer *spk, char **parameters) {
spk->setVolume = spk_setVolume;
spk->setRate = spk_setRate;
if ((spk->driver.data = malloc(sizeof(*spk->driver.data)))) {
memset(spk->driver.data, 0, sizeof(*spk->driver.data));
eciVersion(spk->driver.data->version.text);
logMessage(LOG_INFO, "ViaVoice engine version: %s", spk->driver.data->version.text);
spk->driver.data->version.binary = parseVersion(spk->driver.data->version.text);
spk->driver.data->eci.handle = NULL_ECI_HAND;
spk->driver.data->eci.useSSML = 0;
spk->driver.data->setting.language = NULL;
spk->driver.data->setting.voice = NULL;
spk->driver.data->pcm.buffer = NULL;
spk->driver.data->pcm.stream = NULL;
spk->driver.data->pcm.command = NULL;
spk->driver.data->say.buffer = NULL;
spk->driver.data->say.size = 0;
if ((spk->driver.data->eci.handle = eciNew()) != NULL_ECI_HAND) {
eciRegisterCallback(spk->driver.data->eci.handle, clientCallback, (void *)spk);
spk->driver.data->current.unitType = getEnvironmentParameter(spk, eciRealWorldUnits);
spk->driver.data->current.inputType = getEnvironmentParameter(spk, eciInputType);
if ((spk->driver.data->pcm.buffer = calloc(MAXIMUM_SAMPLES, sizeof(*spk->driver.data->pcm.buffer)))) {
if (eciSetOutputBuffer(spk->driver.data->eci.handle, MAXIMUM_SAMPLES, spk->driver.data->pcm.buffer)) {
setParameters(spk, parameters);
writeAnnotations(spk);
return 1;
} else {
reportError(spk, "eciSetOutputBuffer");
}
free(spk->driver.data->pcm.buffer);
} else {
logMallocError();
}
eciDelete(spk->driver.data->eci.handle);
} else {
logMessage(LOG_ERR, "ViaVoice engine initialization failure");
}
free(spk->driver.data);
spk->driver.data = NULL;
} else {
logMallocError();
}
return 0;
}
static void
spk_destruct (SpeechSynthesizer *spk) {
if (spk->driver.data) {
if (spk->driver.data->eci.handle) eciDelete(spk->driver.data->eci.handle);
if (spk->driver.data->say.buffer) free(spk->driver.data->say.buffer);
if (spk->driver.data->pcm.buffer) free(spk->driver.data->pcm.buffer);
if (spk->driver.data->pcm.command) free(spk->driver.data->pcm.command);
pcmCloseStream(spk);
#ifdef ICONV_NULL
if (spk->driver.data->iconv.handle != ICONV_NULL) iconv_close(spk->driver.data->iconv.handle);
#endif /* ICONV_NULL */
free(spk->driver.data);
spk->driver.data = NULL;
}
}