summaryrefslogtreecommitdiffstats
path: root/pico/tts
diff options
context:
space:
mode:
authorCharles Chen <clchen@google.com>2009-06-22 16:25:25 -0700
committerCharles Chen <clchen@google.com>2009-06-22 17:14:37 -0700
commit1284d937084a20b457c280259fff59391129509a (patch)
tree5630028284c450b56a56b187d9c99cf7ebcee9cc /pico/tts
parentf605ee98e5e03144c25a92af7e5d2a3ec33d375f (diff)
downloadexternal_svox-1284d937084a20b457c280259fff59391129509a.zip
external_svox-1284d937084a20b457c280259fff59391129509a.tar.gz
external_svox-1284d937084a20b457c280259fff59391129509a.tar.bz2
Moving PicoTts plugin under the pico directory of external/svox
Diffstat (limited to 'pico/tts')
-rw-r--r--pico/tts/com_svox_picottsengine.cpp1039
1 files changed, 1039 insertions, 0 deletions
diff --git a/pico/tts/com_svox_picottsengine.cpp b/pico/tts/com_svox_picottsengine.cpp
new file mode 100644
index 0000000..3e29695
--- /dev/null
+++ b/pico/tts/com_svox_picottsengine.cpp
@@ -0,0 +1,1039 @@
+/*
+ * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * History:
+ * 2009-05-18 -- initial version
+ * 2009-06-04 -- updated for new TtsEngine interface
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define LOG_TAG "SVOX Pico Engine"
+
+#include <utils/Log.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <tts/TtsEngine.h>
+#include <picoapi.h>
+#include <picodefs.h>
+
+using namespace android;
+
+/* adaptation layer defines */
+#define PICO_MEM_SIZE 2500000
+#define PICO_MIN_RATE 20
+/* speaking rate */
+#define PICO_DEF_RATE 100
+#define PICO_MAX_RATE 500
+#define PICO_MIN_PITCH 50
+/* speaking pitch */
+#define PICO_DEF_PITCH 100
+#define PICO_MAX_PITCH 200
+#define PICO_MIN_VOLUME 0
+/* speaking volume */
+#define PICO_DEF_VOLUME 250
+#define PICO_MAX_VOLUME 500
+/* string constants */
+#define MAX_OUTBUF_SIZE 128
+const char* PICO_LINGWARE_PATH = "/sdcard/svox/";
+const char* PICO_VOICE_NAME = "PicoVoice";
+const char* PICO_SPEED_OPEN_TAG = "<speed level='%d'>";
+const char* PICO_SPEED_CLOSE_TAG = "</speed>";
+const char* PICO_PITCH_OPEN_TAG = "<pitch level='%d'>";
+const char* PICO_PITCH_CLOSE_TAG = "</pitch>";
+const char* PICO_VOLUME_OPEN_TAG = "<volume level='%d'>";
+const char* PICO_VOLUME_CLOSE_TAG = "</volume>";
+const char* PICO_PHONEME_OPEN_TAG = "<phoneme ph=\"%s\">";
+
+/* supported voices */
+const char* picoSupportedLangIso3[] = { "eng", "eng", "deu", "spa", "fra", "ita" };
+const char* picoSupportedCountryIso3[] = { "USA", "GBR", "DEU", "ESP", "FRA", "ITA" };
+const char* picoSupportedLang[] = { "en-rUS", "en-rGB", "de-rDE", "es-rES", "fr-rFR", "it-rIT" };
+const char* picoInternalLang[] = { "en-US", "en-GB", "de-DE", "es-ES", "fr-FR", "it-IT" };
+const char* picoInternalTaLingware[] = { "en-US_ta.bin", "en-GB_ta.bin", "de-DE_ta.bin", "es-ES_ta.bin", "fr-FR_ta.bin", "it-IT_ta.bin" };
+const char* picoInternalSgLingware[] = { "en-US_lh0_sg.bin", "en-GB_kh0_sg.bin", "de-DE_gl0_sg.bin", "es-ES_zl0_sg.bin", "fr-FR_nk0_sg.bin", "it-IT_cm0_sg.bin" };
+const char* picoInternalUtppLingware[] = { "en-US_utpp.bin", "en-GB_utpp.bin", "de-DE_utpp.bin", "es-ES_utpp.bin", "fr-FR_utpp.bin", "it-IT_utpp.bin" };
+const int picoNumSupportedLang = 6;
+
+/* supported properties */
+const char* picoSupportedProperties[] = { "language", "rate", "pitch", "volume" };
+const int picoNumSupportedProperties = 4;
+
+
+/* adapation layer global variables */
+synthDoneCB_t* picoSynthDoneCBPtr;
+void* picoMemArea = NULL;
+pico_System picoSystem = NULL;
+pico_Resource picoTaResource = NULL;
+pico_Resource picoSgResource = NULL;
+pico_Resource picoUtppResource = NULL;
+pico_Engine picoEngine = NULL;
+pico_Char* picoTaFileName = NULL;
+pico_Char* picoSgFileName = NULL;
+pico_Char* picoUtppFileName = NULL;
+pico_Char* picoTaResourceName = NULL;
+pico_Char* picoSgResourceName = NULL;
+pico_Char* picoUtppResourceName = NULL;
+int picoSynthAbort = 0;
+char* picoProp_currLang = NULL; /* current language */
+int picoProp_currRate = PICO_DEF_RATE; /* current rate */
+int picoProp_currPitch = PICO_DEF_PITCH; /* current pitch */
+int picoProp_currVolume = PICO_DEF_VOLUME; /* current volume */
+
+
+
+/* internal helper functions */
+
+/** checkForLanguage
+ * Check if the requested language is among the supported languages.
+ * @language - the language to check, either in xx or xx-rYY format
+ * return index of the language, or -1 if not supported.
+*/
+static int checkForLanguage( const char * language )
+{
+ int found = -1; /* language not found */
+
+ /* Verify that the requested locale is a locale that we support. */
+ for (int i = 0; i < picoNumSupportedLang; i++)
+ {
+ if (strcmp(language, picoSupportedLang[i]) == 0)
+ {
+ found = i;
+ break;
+ }
+ };
+ if (found < 0)
+ {
+ /* We didn't find an exact match; it may have been specified with only the first 2 characters.
+ This could overmatch ISO 639-3 language codes. */
+ for (int i = 0; i < picoNumSupportedLang; i++)
+ {
+ if (strncmp(language, picoSupportedLang[i], 2) == 0)
+ {
+ found = i;
+ break;
+ }
+ }
+ if (found < 0)
+ {
+ LOGE("TtsEngine::set language called with unsupported language");
+ }
+ };
+ return found;
+}
+
+
+/** cleanResources
+ * Unloads any loaded Pico resources.
+*/
+static void cleanResources( void )
+{
+ if (picoEngine)
+ {
+ pico_disposeEngine( picoSystem, &picoEngine );
+ pico_releaseVoiceDefinition(picoSystem, (pico_Char*)PICO_VOICE_NAME);
+ picoEngine = NULL;
+ }
+ if (picoUtppResource)
+ {
+ pico_unloadResource( picoSystem, &picoUtppResource );
+ picoUtppResource = NULL;
+ }
+ if (picoTaResource)
+ {
+ pico_unloadResource( picoSystem, &picoTaResource );
+ picoTaResource = NULL;
+ }
+ if (picoSgResource)
+ {
+ pico_unloadResource( picoSystem, &picoSgResource );
+ picoSgResource = NULL;
+ }
+}
+
+
+/** cleanFiles
+ * Frees any memory allocated for file and resource strings.
+*/
+static void cleanFiles( void )
+{
+ if (picoProp_currLang)
+ {
+ free(picoProp_currLang);
+ picoProp_currLang = NULL;
+ }
+
+ if (picoTaFileName)
+ {
+ free(picoTaFileName);
+ picoTaFileName = NULL;
+ }
+
+ if (picoSgFileName)
+ {
+ free(picoSgFileName);
+ picoSgFileName = NULL;
+ }
+
+ if (picoUtppFileName)
+ {
+ free(picoUtppFileName);
+ picoUtppFileName = NULL;
+ }
+
+ if (picoTaResourceName)
+ {
+ free(picoTaResourceName);
+ picoTaResourceName = NULL;
+ }
+
+ if (picoSgResourceName)
+ {
+ free(picoSgResourceName);
+ picoSgResourceName = NULL;
+ }
+
+ if (picoUtppResourceName)
+ {
+ free(picoUtppResourceName);
+ picoUtppResourceName = NULL;
+ }
+}
+
+/** doLanguageSwitchFromLangIndex
+ * Switch to requested language. If language is already loaded it returns
+ * immediately, if another language is loaded this will first be unloaded
+ * and the new one then loaded. If no language is loaded the requested will be loaded.
+ * @langIndex - the index of the language to load, which is guaranteed to be supported.
+ * return TTS_SUCCESS or TTS_FAILURE
+ */
+static tts_result doLanguageSwitchFromLangIndex(int langIndex)
+{
+ // if we already have a loaded language, check if it's the same one as requested
+ if (picoProp_currLang && (strcmp(picoProp_currLang, picoSupportedLang[langIndex]) == 0))
+ {
+ LOGI("Language already loaded (%s == %s)", picoProp_currLang, picoSupportedLang[langIndex]);
+ return TTS_SUCCESS;
+ }
+
+ // not the same language, unload the current one first
+ cleanResources();
+
+ // allocate memory for file and resource names
+ cleanFiles();
+ picoProp_currLang = (char*)malloc(10);
+ picoTaFileName = (pico_Char*)malloc(PICO_MAX_DATAPATH_NAME_SIZE + PICO_MAX_FILE_NAME_SIZE);
+ picoSgFileName = (pico_Char*)malloc(PICO_MAX_DATAPATH_NAME_SIZE + PICO_MAX_FILE_NAME_SIZE);
+ picoUtppFileName = (pico_Char*)malloc(PICO_MAX_DATAPATH_NAME_SIZE + PICO_MAX_FILE_NAME_SIZE);
+ picoTaResourceName = (pico_Char*)malloc(PICO_MAX_RESOURCE_NAME_SIZE);
+ picoSgResourceName = (pico_Char*)malloc(PICO_MAX_RESOURCE_NAME_SIZE);
+ picoUtppResourceName = (pico_Char*)malloc(PICO_MAX_RESOURCE_NAME_SIZE);
+
+ // set path and file names for resource files
+ strcpy((char*)picoTaFileName, PICO_LINGWARE_PATH);
+ strcat((char*)picoTaFileName, (const char*)picoInternalTaLingware[langIndex]);
+ strcpy((char*)picoSgFileName, PICO_LINGWARE_PATH);
+ strcat((char*)picoSgFileName, (const char*)picoInternalSgLingware[langIndex]);
+ strcpy((char*)picoUtppFileName, PICO_LINGWARE_PATH);
+ strcat((char*)picoUtppFileName, (const char*)picoInternalUtppLingware[langIndex]);
+
+ // load text analysis Lingware resource file
+ int ret = pico_loadResource(picoSystem, picoTaFileName, &picoTaResource);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to load textana resource for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // load signal generation Lingware resource file
+ ret = pico_loadResource(picoSystem, picoSgFileName, &picoSgResource);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to load siggen resource for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // Load utpp Lingware resource file if exists - NOTE: this file is optional
+ // and is currently not used. Loading is only attempted for future compatibility.
+ // If this file is not present the loading will still succeed.
+ ret = pico_loadResource(picoSystem, picoUtppFileName, &picoUtppResource);
+ if (PICO_OK != ret && ret != PICO_EXC_CANT_OPEN_FILE)
+ {
+ LOGE("Failed to load utpp resource for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // Get text analysis resource name
+ ret = pico_getResourceName(picoSystem, picoTaResource, (char*)picoTaResourceName);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to get textana resource name for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // Get signal generation resource name
+ ret = pico_getResourceName(picoSystem, picoSgResource, (char*)picoSgResourceName);
+ if (PICO_OK == ret && picoUtppResource != NULL)
+ {
+ // Get utpp resource name - optional: see note above
+ ret = pico_getResourceName(picoSystem, picoUtppResource, (char*)picoUtppResourceName);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to get utpp resource name for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+ }
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to get siggen resource name for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // create a voice definition
+ ret = pico_createVoiceDefinition(picoSystem, (const pico_Char*)PICO_VOICE_NAME);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to create voice for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // add text analysis resource to voice
+ ret = pico_addResourceToVoiceDefinition(picoSystem, (const pico_Char*)PICO_VOICE_NAME, picoTaResourceName);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to add textana resource to voice for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ // add signal generation resource to voice
+ ret = pico_addResourceToVoiceDefinition(picoSystem, (const pico_Char*)PICO_VOICE_NAME, picoSgResourceName);
+ if (PICO_OK == ret && picoUtppResource != NULL)
+ {
+ // add utpp resource to voice - optional: see note above
+ ret = pico_addResourceToVoiceDefinition(picoSystem, (const pico_Char*)PICO_VOICE_NAME, picoUtppResourceName);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to add utpp resource to voice for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+ }
+
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to add siggen resource to voice for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ ret = pico_newEngine(picoSystem, (const pico_Char*)PICO_VOICE_NAME, &picoEngine);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to create engine for %s [%d]", picoSupportedLang[langIndex], ret);
+ cleanResources();
+ cleanFiles();
+ return TTS_FAILURE;
+ }
+
+ strcpy(picoProp_currLang, picoSupportedLang[langIndex]);
+
+ LOGI("loaded %s successfully", picoProp_currLang);
+
+ return TTS_SUCCESS;
+}
+
+/** doLanguageSwitch
+ * Switch to requested language. If language is already loaded it returns
+ * immediately, if another language is loaded this will first be unloaded
+ * and the new one then loaded. If no language is loaded the requested will be loaded.
+ * @language - the language to check, either in xx or xx-rYY format (i.e "en" or "en-rUS")
+ * return TTS_SUCCESS or TTS_FAILURE
+*/
+static tts_result doLanguageSwitch(const char* language)
+{
+ // load new language
+ int langIndex = checkForLanguage(language);
+ if (langIndex < 0)
+ {
+ LOGE("Tried to swith to non-supported language %s", language);
+ return TTS_FAILURE;
+ }
+ LOGI("Found supported language %s", picoSupportedLang[langIndex]);
+
+ return doLanguageSwitchFromLangIndex( langIndex );
+}
+
+/** doAddProperties
+ * Add <speed>, <pitch> and <volume> tags to text, if the properties have been set to non-default values,
+ * and return the new string. The calling function is responsible for freeing the returned string.
+ * @str - text to apply tags to
+ * return new string with tags applied
+*/
+static char* doAddProperties(const char* str)
+{
+ char* data = NULL;
+ int haspitch = 0, hasspeed = 0, hasvol = 0;
+ int textlen = strlen(str) + 1;
+
+ if (picoProp_currPitch != PICO_DEF_PITCH) /* non-default pitch */
+ {
+ textlen += strlen(PICO_PITCH_OPEN_TAG) + 5;
+ textlen += strlen(PICO_PITCH_CLOSE_TAG);
+ haspitch = 1;
+ }
+ if (picoProp_currRate != PICO_DEF_RATE) /* non-default rate */
+ {
+ textlen += strlen(PICO_SPEED_OPEN_TAG) + 5;
+ textlen += strlen(PICO_SPEED_CLOSE_TAG);
+ hasspeed = 1;
+ }
+ if (picoProp_currVolume != PICO_DEF_VOLUME) /* non-default volume */
+ {
+ textlen += strlen(PICO_VOLUME_OPEN_TAG) + 5;
+ textlen += strlen(PICO_VOLUME_CLOSE_TAG);
+ hasvol = 1;
+ }
+
+ data = (char*)malloc(textlen);
+ if (!data)
+ {
+ return NULL;
+ }
+ memset(data, 0, textlen); /* clear it */
+ if (haspitch)
+ {
+ char* tmp = (char*)malloc(strlen(PICO_PITCH_OPEN_TAG) + strlen(PICO_PITCH_CLOSE_TAG) + 5);
+ sprintf(tmp, PICO_PITCH_OPEN_TAG, picoProp_currPitch);
+ strcat(data, tmp);
+ free(tmp);
+ }
+
+ if (hasspeed)
+ {
+ char* tmp = (char*)malloc(strlen(PICO_SPEED_OPEN_TAG) + strlen(PICO_SPEED_CLOSE_TAG) + 5);
+ sprintf(tmp, PICO_SPEED_OPEN_TAG, picoProp_currRate);
+ strcat(data, tmp);
+ free(tmp);
+ }
+
+ if (hasvol)
+ {
+ char* tmp = (char*)malloc(strlen(PICO_VOLUME_OPEN_TAG) + strlen(PICO_VOLUME_CLOSE_TAG) + 5);
+ sprintf(tmp, PICO_VOLUME_OPEN_TAG, picoProp_currVolume);
+ strcat(data, tmp);
+ free(tmp);
+ }
+
+ strcat(data, str);
+
+ if (hasvol)
+ {
+ strcat(data, PICO_VOLUME_CLOSE_TAG);
+ }
+
+ if (hasspeed)
+ {
+ strcat(data, PICO_SPEED_CLOSE_TAG);
+ }
+
+ if (haspitch)
+ {
+ strcat(data, PICO_PITCH_CLOSE_TAG);
+ }
+
+ return data;
+}
+
+
+/* API function implementations */
+
+/** init
+ * Allocates Pico memory block and initializes Pico system.
+ * synthDoneCBPtr - Pointer to callback function which will receive generated samples
+ * return tts_result
+*/
+tts_result TtsEngine::init( synthDoneCB_t synthDoneCBPtr )
+{
+ if (synthDoneCBPtr == NULL)
+ {
+ LOGE("Callback pointer is NULL");
+ return TTS_FAILURE;
+ }
+
+ picoMemArea = malloc(PICO_MEM_SIZE);
+ if (!picoMemArea)
+ {
+ LOGE("Failed to allocate memory for Pico system");
+ return TTS_FAILURE;
+ }
+
+ pico_Status ret = pico_initialize(picoMemArea, PICO_MEM_SIZE, &picoSystem);
+ if (PICO_OK != ret)
+ {
+ LOGE("Failed to initialize Pico system");
+ free(picoMemArea);
+ picoMemArea = NULL;
+ return TTS_FAILURE;
+ }
+
+ picoSynthDoneCBPtr = synthDoneCBPtr;
+ return TTS_SUCCESS;
+}
+
+
+/** shutdown
+ * Unloads all Pico resources; terminates Pico system and frees Pico memory block.
+ * return tts_result
+*/
+tts_result TtsEngine::shutdown( void )
+{
+ cleanResources();
+
+ if (picoSystem)
+ {
+ pico_terminate(&picoSystem);
+ picoSystem = NULL;
+ }
+ if (picoMemArea)
+ {
+ free(picoMemArea);
+ picoMemArea = NULL;
+ }
+
+ cleanFiles();
+
+ return TTS_SUCCESS;
+}
+
+/** loadLanguage
+ * Load a new language.
+ * @value - language string in xx or xx-rYY format (i.e. "en" or "en-rUS")
+ * @size - size of value
+ * return tts_result
+*/
+tts_result TtsEngine::loadLanguage( const char * value, const size_t size )
+{
+ return setProperty("language", value, size);
+}
+
+/** setLanguage
+ * Load a new language.
+ * @lang - string with ISO 3 letter language code.
+ * @country - string with ISO 3 letter country code .
+ * @variant - string with language variant for that language and country pair.
+ * return tts_result
+ */
+tts_result TtsEngine::setLanguage(const char *lang, const char *country, const char *variant) {
+ if (lang == NULL) {
+ LOGE("TtsEngine::setLanguage called with NULL language");
+ return TTS_FAILURE;
+ }
+
+ // we look for a match on the language first
+ // then we look for a match on the country.
+ // if no match on the language:
+ // return an error
+ // if match on the language, but no match on the country:
+ // load the language found for the language match
+ // if match on the language, and match on the country:
+ // load the language found for the country match
+
+ // find a match on the language
+ int langIndex = -1;
+ for (int i = 0; i < picoNumSupportedLang; i++)
+ {
+ if (strcmp(lang, picoSupportedLangIso3[i]) == 0) {
+ langIndex = i;
+ break;
+ }
+ }
+ if (langIndex < 0) {
+ // language isn't supported
+ LOGE("TtsEngine::setLanguage called with unsupported language");
+ return TTS_FAILURE;
+ }
+
+ // find a match on the country
+ if (country != NULL) {
+ int countryIndex = -1;
+ for (int i = langIndex; i < picoNumSupportedLang; i++) {
+ if ((strcmp(lang, picoSupportedLangIso3[i]) == 0)
+ && (strcmp(country, picoSupportedCountryIso3[i]) == 0)) {
+ countryIndex = i;
+ break;
+ }
+ }
+
+ if (countryIndex < 0) {
+ // we didn't find a match on the country, but we had a match on the language,
+ // use that language
+ LOGI("TtsEngine::setLanguage found matching language(%s) but not matching country(%s).",
+ lang, country);
+ } else {
+ // we have a match on the language and the country
+ langIndex = countryIndex;
+ }
+ }
+
+ return doLanguageSwitchFromLangIndex( langIndex );
+}
+
+
+/** getLanguage
+ * Get the currently loaded language - if any.
+ * @value - buffer which will receive value
+ * @iosize - size of value - if value is too small to contain the return string, this will contain the actual size needed
+ * return tts_result
+*/
+tts_result TtsEngine::getLanguage( char * value, size_t * iosize )
+{
+ return getProperty("language", value, iosize);
+}
+
+
+/** setProperty
+ * Set property. The supported properties are: language, rate, pitch and volume.
+ * @property - name of property to set
+ * @value - value to set
+ * @size - size of value
+ * return tts_result
+*/
+tts_result TtsEngine::setProperty( const char * property, const char * value, const size_t size )
+{
+ /* Sanity check */
+ if (property == NULL)
+ {
+ LOGE("setProperty called with property NULL");
+ return TTS_PROPERTY_UNSUPPORTED;
+ }
+
+ if (value == NULL)
+ {
+ LOGE("setProperty called with value NULL");
+ return TTS_VALUE_INVALID;
+ }
+
+ if (strncmp(property, "language", 8) == 0)
+ {
+ // verify it's in correct format
+ if (strlen(value) != 2 && strlen(value) != 6)
+ {
+ LOGE("change language called with incorrect format");
+ return TTS_VALUE_INVALID;
+ }
+
+ // try to switch to specified language
+ if (doLanguageSwitch(value) == TTS_FAILURE)
+ {
+ LOGE("failed to load language");
+ return TTS_FAILURE;
+ }
+ else
+ {
+ return TTS_SUCCESS;
+ }
+ }
+ else if (strncmp(property, "rate", 4) == 0)
+ {
+ int rate = atoi(value);
+ if (rate < PICO_MIN_RATE) rate = PICO_MIN_RATE;
+ if (rate > PICO_MAX_RATE) rate = PICO_MAX_RATE;
+ picoProp_currRate = rate;
+ return TTS_SUCCESS;
+ }
+ else if (strncmp(property, "pitch", 5) == 0)
+ {
+ int pitch = atoi(value);
+ if (pitch < PICO_MIN_PITCH) pitch = PICO_MIN_PITCH;
+ if (pitch > PICO_MAX_PITCH) pitch = PICO_MAX_PITCH;
+ picoProp_currPitch = pitch;
+ return TTS_SUCCESS;
+ }
+ else if (strncmp(property, "volume", 6) == 0)
+ {
+ int volume = atoi(value);
+ if (volume < PICO_MIN_VOLUME) volume = PICO_MIN_VOLUME;
+ if (volume > PICO_MAX_VOLUME) volume = PICO_MAX_VOLUME;
+ picoProp_currVolume = volume;
+ return TTS_SUCCESS;
+ }
+
+ return TTS_PROPERTY_UNSUPPORTED;
+}
+
+
+/** getProperty
+ * Get the property. Supported properties are: language, rate, pitch and volume.
+ * @property - name of property to get
+ * @value - buffer which will receive value of property
+ * @iosize - size of value - if size is too small on return this will contain actual size needed
+ * return tts_result
+*/
+tts_result TtsEngine::getProperty(const char *property, char *value, size_t* iosize)
+{
+ /* sanity check */
+ if (property == NULL)
+ {
+ LOGE("getProperty called with property NULL");
+ return TTS_PROPERTY_UNSUPPORTED;
+ }
+
+ if (value == NULL)
+ {
+ LOGE("getProperty called with value NULL");
+ return TTS_VALUE_INVALID;
+ }
+
+ if (strncmp(property, "language", 8) == 0)
+ {
+ if (picoProp_currLang == NULL)
+ {
+ strcpy(value, "");
+ }
+ else
+ {
+ if (*iosize < strlen(picoProp_currLang)+1)
+ {
+ *iosize = strlen(picoProp_currLang) + 1;
+ return TTS_PROPERTY_SIZE_TOO_SMALL;
+ }
+ strcpy(value, picoProp_currLang);
+ }
+ return TTS_SUCCESS;
+ }
+ else if (strncmp(property, "rate", 4) == 0)
+ {
+ char tmprate[4];
+ sprintf(tmprate, "%d", picoProp_currRate);
+ if (*iosize < strlen(tmprate)+1)
+ {
+ *iosize = strlen(tmprate) + 1;
+ return TTS_PROPERTY_SIZE_TOO_SMALL;
+ }
+ strcpy(value, tmprate);
+ return TTS_SUCCESS;
+ }
+ else if (strncmp(property, "pitch", 5) == 0)
+ {
+ char tmppitch[4];
+ sprintf(tmppitch, "%d", picoProp_currPitch);
+ if (*iosize < strlen(tmppitch)+1)
+ {
+ *iosize = strlen(tmppitch) + 1;
+ return TTS_PROPERTY_SIZE_TOO_SMALL;
+ }
+ strcpy(value, tmppitch);
+ return TTS_SUCCESS;
+ }
+ else if (strncmp(property, "volume", 6) == 0)
+ {
+ char tmpvol[4];
+ sprintf(tmpvol, "%d", picoProp_currVolume);
+ if (*iosize < strlen(tmpvol)+1)
+ {
+ *iosize = strlen(tmpvol) + 1;
+ return TTS_PROPERTY_SIZE_TOO_SMALL;
+ }
+ strcpy(value, tmpvol);
+ return TTS_SUCCESS;
+ }
+ else
+ {
+ LOGE("Unsupported property");
+ return TTS_PROPERTY_UNSUPPORTED;
+ }
+}
+
+
+/** synthesizeText
+ * Synthesizes a text string.
+ * @text - text to synthesize
+ * @buffer - buffer which will receive generated samples
+ * @bufferSize - size of buffer
+ * @userdata - pointer to user data which will be passed back to callback function
+ * return tts_result
+*/
+tts_result TtsEngine::synthesizeText(const char *text, int8_t *buffer, size_t bufferSize, void *userdata)
+{
+ pico_Char* inp = NULL;
+ pico_Char* local_text = NULL;
+ short outbuf[MAX_OUTBUF_SIZE/2];
+ pico_Int16 bytes_sent, bytes_recv, text_remaining, out_data_type;
+ pico_Status ret;
+ picoSynthAbort = 0;
+
+ if (text == NULL)
+ {
+ LOGE("synthesizeText called with NULL string");
+ return TTS_FAILURE;
+ }
+
+ if (buffer == NULL)
+ {
+ LOGE("synthesizeText called with NULL buffer");
+ return TTS_FAILURE;
+ }
+
+ /* Add property tags to the string - if any. */
+ local_text = (pico_Char*)doAddProperties(text);
+ if (!local_text)
+ {
+ LOGE("Failed to allocate memory for text string");
+ return TTS_FAILURE;
+ }
+
+ text_remaining = strlen((const char*)local_text) + 1;
+
+ inp = (pico_Char*)local_text;
+
+ size_t bufused = 0;
+
+ /* synthesis loop */
+ while (text_remaining)
+ {
+ if (picoSynthAbort)
+ {
+ ret = pico_resetEngine(picoEngine);
+ break;
+ }
+
+ /* Feed the text into the engine. */
+ ret = pico_putTextUtf8(picoEngine, inp, text_remaining, &bytes_sent);
+ if (ret != PICO_OK)
+ {
+ LOGE("Error synthesizing string '%s': [%d]", text, ret);
+ if (local_text) free(local_text);
+ return TTS_FAILURE;
+ }
+
+ text_remaining -= bytes_sent;
+ inp += bytes_sent;
+ do
+ {
+ if (picoSynthAbort)
+ {
+ break;
+ }
+ /* Retrieve the samples and add them to the buffer. */
+ ret = pico_getData(picoEngine, (void*)outbuf, MAX_OUTBUF_SIZE, &bytes_recv, &out_data_type);
+ if (bytes_recv)
+ {
+ if ((bufused + bytes_recv) <= bufferSize)
+ {
+ memcpy(buffer+bufused, (int8_t*)outbuf, bytes_recv);
+ bufused += bytes_recv;
+ }
+ else
+ {
+ /* The buffer filled; pass this on to the callback function. */
+ int cbret = picoSynthDoneCBPtr(userdata, 16000, AudioSystem::PCM_16_BIT, 1, buffer, bufused, TTS_SYNTH_PENDING);
+ if (cbret == TTS_CALLBACK_HALT)
+ {
+ LOGI("Halt requested by caller. Halting.");
+ picoSynthAbort = 1;
+ break;
+ }
+ bufused = 0;
+ memcpy(buffer, (int8_t*)outbuf, bytes_recv);
+ bufused += bytes_recv;
+ }
+ }
+ } while (PICO_STEP_BUSY == ret);
+
+ /* The synthesis is finished; notify the caller and pass the remaining samples.
+ Use 16 KHz, 16-bit samples. */
+ if (!picoSynthAbort)
+ {
+ picoSynthDoneCBPtr( userdata, 16000, AudioSystem::PCM_16_BIT, 1, buffer, bufused, TTS_SYNTH_DONE);
+ }
+ picoSynthAbort = 0;
+
+ if (ret != PICO_STEP_IDLE)
+ {
+ LOGE("Error occurred during synthesis [%d]", ret);
+ if (local_text) free(local_text);
+ return TTS_FAILURE;
+ }
+ }
+
+ if (local_text) free(local_text);
+ return TTS_SUCCESS;
+}
+
+
+/** synthesizeIpa
+ * Synthesizes a phonetic string in IPA format.
+ * @ipa - phonetic string to synthesize
+ * @buffer - buffer which will receive generated samples
+ * @bufferSize - size of buffer
+ * @userdata - pointer to user data which will be passed back to callback function
+ * return tts_result
+*/
+tts_result TtsEngine::synthesizeIpa( const char * ipa, int8_t * buffer, size_t bufferSize, void * userdata )
+{
+ pico_Char* inp = NULL;
+ pico_Char* local_text = NULL;
+ short outbuf[MAX_OUTBUF_SIZE/2];
+ pico_Int16 bytes_sent, bytes_recv, text_remaining, out_data_type;
+ pico_Status ret;
+
+ picoSynthAbort = 0;
+ if (ipa == NULL)
+ {
+ LOGE("synthesizeIpa called with NULL string");
+ return TTS_FAILURE;
+ }
+
+ if (buffer == NULL)
+ {
+ LOGE("synthesizeIpa called with NULL buffer");
+ return TTS_FAILURE;
+ }
+
+ /* Append phoneme tag. %%%
+ <phoneme ph="xxx"/> */
+
+ /* Add property tags to the string - if any. */
+ local_text = (pico_Char*)doAddProperties( ipa );
+ if (!local_text)
+ {
+ LOGE("Failed to allocate memory for text string");
+ return TTS_FAILURE;
+ }
+
+ text_remaining = strlen((const char*)local_text) + 1;
+
+ inp = (pico_Char*)local_text;
+
+ size_t bufused = 0;
+
+ /* synthesis loop */
+ while (text_remaining)
+ {
+ if (picoSynthAbort)
+ {
+ ret = pico_resetEngine( picoEngine );
+ break;
+ }
+
+ /* Feed the text into the engine. */
+ ret = pico_putTextUtf8( picoEngine, inp, text_remaining, &bytes_sent );
+ if (ret != PICO_OK)
+ {
+ LOGE("Error synthesizing string '%s': [%d]", ipa, ret);
+ if (local_text) free(local_text);
+ return TTS_FAILURE;
+ }
+
+ /* Process the remaining string. */
+ text_remaining -= bytes_sent;
+ inp += bytes_sent;
+ do
+ {
+ if (picoSynthAbort)
+ {
+ break;
+ }
+ /* Retrieve the samples and add them to the buffer. */
+ ret = pico_getData( picoEngine, (void*)outbuf, MAX_OUTBUF_SIZE, &bytes_recv, &out_data_type );
+ if (bytes_recv)
+ {
+ if ((bufused + bytes_recv) <= bufferSize)
+ {
+ memcpy(buffer+bufused, (int8_t*)outbuf, bytes_recv);
+ bufused += bytes_recv;
+ }
+ else
+ {
+ /* The buffer filled; pass this on to the callback function. */
+ int cbret = picoSynthDoneCBPtr(userdata, 16000, AudioSystem::PCM_16_BIT, 1, buffer, bufused, TTS_SYNTH_PENDING);
+ if (cbret == TTS_CALLBACK_HALT)
+ {
+ LOGI("Halt requested by caller. Halting.");
+ picoSynthAbort = 1;
+ break;
+ }
+ bufused = 0;
+ memcpy(buffer, (int8_t*)outbuf, bytes_recv);
+ bufused += bytes_recv;
+ }
+ }
+ } while (PICO_STEP_BUSY == ret);
+
+ /* The synthesis is finished; notify the caller and pass the remaining samples.
+ Use 16 KHz, 16-bit samples. */
+ if (!picoSynthAbort)
+ {
+ picoSynthDoneCBPtr( userdata, 16000, AudioSystem::PCM_16_BIT, 1, buffer, bufused, TTS_SYNTH_DONE );
+ }
+ picoSynthAbort = 0; /* succeeded */
+
+ if (ret != PICO_STEP_IDLE)
+ {
+ LOGE("Error occurred during synthesis [%d]", ret);
+ if (local_text) free(local_text);
+ return TTS_FAILURE;
+ }
+ }
+
+ if (local_text)
+ free(local_text);
+ return TTS_SUCCESS; /* succeeded */
+}
+
+
+/** stop
+ * Aborts the running synthesis.
+ * return tts_result
+*/
+tts_result TtsEngine::stop()
+{
+ picoSynthAbort = 1;
+ return TTS_SUCCESS;
+}
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TtsEngine* getTtsEngine()
+{
+ return new TtsEngine();
+}
+
+#ifdef __cplusplus
+}
+#endif
+