| /* |
| * 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; |
| } |
| } |