From 1de60cdc159008ce34ab6fc74a39de38a880c790 Mon Sep 17 00:00:00 2001 From: "Reece H. Dunn" Date: Thu, 5 Sep 2013 14:06:57 +0100 Subject: [PATCH] Rework the 'Punctuation level' (now 'Speak punctuation') preference to include a custom punctuation list. --- android/jni/Android.mk | 2 + android/jni/jni/eSpeakService.cpp | 88 ++++++++++++ .../layout/speak_punctuation_preference.xml | 39 ++++++ android/res/values/donottranslate.xml | 6 - android/res/values/strings.xml | 16 +-- android/res/xml/preferences.xml | 8 -- .../espeak/SpeakPunctuationPreference.java | 129 ++++++++++++++++++ .../com/reecedunn/espeak/SpeechSynthesis.java | 12 +- .../src/com/reecedunn/espeak/TtsService.java | 1 + .../reecedunn/espeak/TtsSettingsActivity.java | 15 ++ 10 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 android/res/layout/speak_punctuation_preference.xml create mode 100644 android/src/com/reecedunn/espeak/SpeakPunctuationPreference.java diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 7a211908..5f19f824 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -1,6 +1,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) +LOCAL_CFLAGS = -std=c++11 + # ucd-tools wide-character compatibility support: UCDTOOLS_SRC_PATH := ../../ucd-tools/src diff --git a/android/jni/jni/eSpeakService.cpp b/android/jni/jni/eSpeakService.cpp index eff2c39c..a2c699ad 100644 --- a/android/jni/jni/eSpeakService.cpp +++ b/android/jni/jni/eSpeakService.cpp @@ -31,6 +31,77 @@ #include #include +/** @name Java to Wide String Helpers + * @brief These are helpers for converting a jstring to wchar_t*. + * + * This assumes that wchar_t is a 32-bit (UTF-32) value. + */ +//@{ + +const char *utf8_read(const char *in, wchar_t &c) +{ + if (uint8_t(*in) < 0x80) + c = *in++; + else switch (uint8_t(*in) & 0xF0) + { + default: + c = uint8_t(*in++) & 0x1F; + c = (c << 6) + (uint8_t(*in++) & 0x3F); + break; + case 0xE0: + c = uint8_t(*in++) & 0x0F; + c = (c << 6) + (uint8_t(*in++) & 0x3F); + c = (c << 6) + (uint8_t(*in++) & 0x3F); + break; + case 0xF0: + c = uint8_t(*in++) & 0x07; + c = (c << 6) + (uint8_t(*in++) & 0x3F); + c = (c << 6) + (uint8_t(*in++) & 0x3F); + c = (c << 6) + (uint8_t(*in++) & 0x3F); + break; + } + return in; +} + +class unicode_string +{ + static_assert(sizeof(wchar_t) == 4, "wchar_t is not UTF-32"); +public: + unicode_string(JNIEnv *env, jstring str); + ~unicode_string(); + + const wchar_t *c_str() const { return mString; } +private: + wchar_t *mString; +}; + +unicode_string::unicode_string(JNIEnv *env, jstring str) + : mString(NULL) +{ + if (str == NULL) return; + + const char *utf8 = env->GetStringUTFChars(str, NULL); + mString = (wchar_t *)malloc(strlen(utf8) + 1); + + const char *utf8_current = utf8; + wchar_t *utf32_current = mString; + while (*utf8_current) + { + utf8_current = utf8_read(utf8_current, *utf32_current); + ++utf32_current; + } + *utf32_current = 0; + + env->ReleaseStringUTFChars(str, utf8); +} + +unicode_string::~unicode_string() +{ + if (mString) free(mString); +} + +//@} + #define LOG_TAG "eSpeakService" #define DEBUG true @@ -314,6 +385,23 @@ JNICALL Java_com_reecedunn_espeak_SpeechSynthesis_nativeGetParameter( return espeak_GetParameter((espeak_PARAMETER)parameter, (int)current); } +JNIEXPORT jboolean +JNICALL Java_com_reecedunn_espeak_SpeechSynthesis_nativeSetPunctuationCharacters( + JNIEnv *env, jobject object, jstring characters) { + if (DEBUG) LOGV("%s)", __FUNCTION__); + + unicode_string list(env, characters); + const espeak_ERROR result = espeak_SetPunctuationList(list.c_str()); + switch (result) { + case EE_OK: return JNI_TRUE; + case EE_INTERNAL_ERROR: LOGE("espeak_SetPunctuationList: internal error."); break; + case EE_BUFFER_FULL: LOGE("espeak_SetPunctuationList: buffer full."); break; + case EE_NOT_FOUND: LOGE("espeak_SetPunctuationList: not found."); break; + } + + return JNI_FALSE; +} + JNIEXPORT jboolean JNICALL Java_com_reecedunn_espeak_SpeechSynthesis_nativeSynthesize( JNIEnv *env, jobject object, jstring text, jboolean isSsml) { diff --git a/android/res/layout/speak_punctuation_preference.xml b/android/res/layout/speak_punctuation_preference.xml new file mode 100644 index 00000000..6674c4a7 --- /dev/null +++ b/android/res/layout/speak_punctuation_preference.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/res/values/donottranslate.xml b/android/res/values/donottranslate.xml index b7e17cdf..86437a09 100644 --- a/android/res/values/donottranslate.xml +++ b/android/res/values/donottranslate.xml @@ -34,10 +34,4 @@ female-old - - 0 - 1 - 2 - - \ No newline at end of file diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index 2c806d43..7a2f5909 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -111,14 +111,10 @@ Set to default Pitch variation Voice variant - Punctuation level - - - None - All - Some - + Speak punctuation + All + Custom + Custom: %s + None + Punctuation characters diff --git a/android/res/xml/preferences.xml b/android/res/xml/preferences.xml index 7e9a2f6a..fe20d547 100644 --- a/android/res/xml/preferences.xml +++ b/android/res/xml/preferences.xml @@ -9,12 +9,4 @@ android:summary="%s" android:title="@string/espeak_variant" /> - - \ No newline at end of file diff --git a/android/src/com/reecedunn/espeak/SpeakPunctuationPreference.java b/android/src/com/reecedunn/espeak/SpeakPunctuationPreference.java new file mode 100644 index 00000000..bba164cf --- /dev/null +++ b/android/src/com/reecedunn/espeak/SpeakPunctuationPreference.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2013 Reece H. Dunn + * + * 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. + */ + +package com.reecedunn.espeak; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.preference.DialogPreference; +import android.text.Editable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.SeekBar; +import android.widget.TextView; + +public class SpeakPunctuationPreference extends DialogPreference { + private RadioButton mAll; + private RadioButton mCustom; + private EditText mPunctuationCharacters; + + private VoiceSettings mSettings; + + public SpeakPunctuationPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setDialogLayoutResource(R.layout.speak_punctuation_preference); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + } + + public SpeakPunctuationPreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SpeakPunctuationPreference(Context context) { + this(context, null); + } + + public void setVoiceSettings(VoiceSettings settings) { + mSettings = settings; + onDataChanged(mSettings.getPunctuationLevel(), mSettings.getPunctuationCharacters()); + } + + private void onDataChanged(int level, String characters) { + switch (level) { + case SpeechSynthesis.PUNCT_ALL: + callChangeListener(getContext().getText(R.string.punctuation_all)); + break; + case SpeechSynthesis.PUNCT_SOME: + if (characters == null || characters.isEmpty()) { + callChangeListener(getContext().getText(R.string.punctuation_none)); + } else { + callChangeListener(String.format(getContext().getText(R.string.punctuation_custom_fmt).toString(), characters)); + } + break; + case SpeechSynthesis.PUNCT_NONE: + callChangeListener(getContext().getText(R.string.punctuation_none)); + break; + } + } + + @Override + protected View onCreateDialogView() { + View root = super.onCreateDialogView(); + mAll = (RadioButton)root.findViewById(R.id.all); + mCustom = (RadioButton)root.findViewById(R.id.custom); + mPunctuationCharacters = (EditText)root.findViewById(R.id.punctuation_characters); + return root; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + if (mSettings.getPunctuationLevel() == SpeechSynthesis.PUNCT_ALL) { + mAll.toggle(); + } else { + mCustom.toggle(); + } + + mPunctuationCharacters.setText(mSettings.getPunctuationCharacters()); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + Editable text = mPunctuationCharacters.getText(); + String characters = null; + int level; + if (text != null) { + characters = text.toString(); + } + if (characters == null || characters.isEmpty()) { + level = mAll.isChecked() ? SpeechSynthesis.PUNCT_ALL : SpeechSynthesis.PUNCT_NONE; + } else { + level = mAll.isChecked() ? SpeechSynthesis.PUNCT_ALL : SpeechSynthesis.PUNCT_SOME; + } + + onDataChanged(level, characters); + + if (shouldCommit()) { + SharedPreferences.Editor editor = getEditor(); + if (editor != null) { + editor.putString(VoiceSettings.PREF_PUNCTUATION_CHARACTERS, characters); + editor.putString(VoiceSettings.PREF_PUNCTUATION_LEVEL, Integer.toString(level)); + editor.commit(); + } + } + break; + } + super.onClick(dialog, which); + } +} diff --git a/android/src/com/reecedunn/espeak/SpeechSynthesis.java b/android/src/com/reecedunn/espeak/SpeechSynthesis.java index f6b7959e..08067af9 100644 --- a/android/src/com/reecedunn/espeak/SpeechSynthesis.java +++ b/android/src/com/reecedunn/espeak/SpeechSynthesis.java @@ -193,14 +193,18 @@ public class SpeechSynthesis { } } + public void setPunctuationCharacters(String characters) { + nativeSetPunctuationCharacters(characters); + } + /** Don't announce any punctuation characters. */ - public static int PUNCT_NONE = 0; + public static final int PUNCT_NONE = 0; /** Announce every punctuation character. */ - public static int PUNCT_ALL = 1; + public static final int PUNCT_ALL = 1; /** Announce some of the punctuation characters. */ - public static int PUNCT_SOME = 2; + public static final int PUNCT_SOME = 2; public enum UnitType { Percentage, @@ -345,6 +349,8 @@ public class SpeechSynthesis { private native final int nativeGetParameter(int parameter, int current); + private native final boolean nativeSetPunctuationCharacters(String characters); + private native final boolean nativeSynthesize(String text, boolean isSsml); private native final boolean nativeStop(); diff --git a/android/src/com/reecedunn/espeak/TtsService.java b/android/src/com/reecedunn/espeak/TtsService.java index 260267dd..06fe5d6c 100644 --- a/android/src/com/reecedunn/espeak/TtsService.java +++ b/android/src/com/reecedunn/espeak/TtsService.java @@ -241,6 +241,7 @@ public class TtsService extends TextToSpeechService { mEngine.PitchRange.setValue(settings.getPitchRange()); mEngine.Volume.setValue(settings.getVolume()); mEngine.Punctuation.setValue(settings.getPunctuationLevel()); + mEngine.setPunctuationCharacters(settings.getPunctuationCharacters()); mEngine.synthesize(text, text.startsWith("