@@ -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 |
@@ -31,6 +31,77 @@ | |||
#include <TtsEngine.h> | |||
#include <Log.h> | |||
/** @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) { |
@@ -0,0 +1,39 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:layout_width="match_parent" | |||
android:layout_height="match_parent" | |||
android:orientation="vertical" > | |||
<RadioGroup | |||
android:layout_width="fill_parent" | |||
android:layout_height="wrap_content" | |||
android:focusable="false"> | |||
<RadioButton | |||
android:layout_width="match_parent" | |||
android:layout_height="48dp" | |||
android:text="@string/punctuation_all" | |||
android:id="@+id/all" | |||
android:focusable="false" | |||
android:focusableInTouchMode="false" | |||
android:clickable="true" | |||
android:checked="false" /> | |||
<RadioButton | |||
android:layout_width="match_parent" | |||
android:layout_height="48dp" | |||
android:text="@string/punctuation_custom" | |||
android:id="@+id/custom" | |||
android:focusable="false" | |||
android:focusableInTouchMode="false" | |||
android:clickable="true" /> | |||
</RadioGroup> | |||
<EditText | |||
android:layout_width="fill_parent" | |||
android:layout_height="wrap_content" | |||
android:id="@+id/punctuation_characters" | |||
android:hint="@string/punctuation_characters" | |||
android:layout_marginLeft="32dp" /> | |||
</LinearLayout> |
@@ -34,10 +34,4 @@ | |||
<item>female-old</item> | |||
</string-array> | |||
<string-array name="punctuation_level_values"> | |||
<item>0</item> | |||
<item>1</item> | |||
<item>2</item> | |||
</string-array> | |||
</resources> |
@@ -111,14 +111,10 @@ | |||
<string name="resetToDefault">Set to default</string> | |||
<string name="espeak_pitch_range">Pitch variation</string> | |||
<string name="espeak_variant">Voice variant</string> | |||
<string name="espeak_punctuation_level">Punctuation level</string> | |||
<!-- | |||
Source: Punctuation level preference labels. | |||
Description: Labels for possible punctuation level values. | |||
--> | |||
<string-array name="punctuation_level_entries"> | |||
<item>None</item> | |||
<item>All</item> | |||
<item>Some</item> | |||
</string-array> | |||
<string name="espeak_speak_punctuation">Speak punctuation</string> | |||
<string name="punctuation_all">All</string> | |||
<string name="punctuation_custom">Custom</string> | |||
<string name="punctuation_custom_fmt">Custom: %s</string> | |||
<string name="punctuation_none">None</string> | |||
<string name="punctuation_characters">Punctuation characters</string> | |||
</resources> |
@@ -9,12 +9,4 @@ | |||
android:summary="%s" | |||
android:title="@string/espeak_variant" /> | |||
<ListPreference | |||
android:defaultValue="0" | |||
android:entries="@array/punctuation_level_entries" | |||
android:entryValues="@array/punctuation_level_values" | |||
android:key="espeak_punctuation_level" | |||
android:summary="%s" | |||
android:title="@string/espeak_punctuation_level" /> | |||
</PreferenceScreen> |
@@ -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); | |||
} | |||
} |
@@ -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(); |
@@ -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("<speak")); | |||
} | |||
@@ -97,6 +97,18 @@ public class TtsSettingsActivity extends PreferenceActivity { | |||
} | |||
} | |||
private static Preference createSpeakPunctuationPreference(Context context, SpeechSynthesis engine, int titleRes) { | |||
final String title = context.getString(titleRes); | |||
final SpeakPunctuationPreference pref = new SpeakPunctuationPreference(context); | |||
pref.setTitle(title); | |||
pref.setDialogTitle(title); | |||
pref.setOnPreferenceChangeListener(mOnPreferenceChanged); | |||
pref.setPersistent(true); | |||
pref.setVoiceSettings(new VoiceSettings(PreferenceManager.getDefaultSharedPreferences(context), engine)); | |||
return pref; | |||
} | |||
private static Preference createPreference(Context context, SpeechSynthesis.Parameter parameter, String key, int titleRes) { | |||
final String title = context.getString(titleRes); | |||
final int defaultValue = parameter.getDefaultValue(); | |||
@@ -165,6 +177,7 @@ public class TtsSettingsActivity extends PreferenceActivity { | |||
SpeechSynthesis engine = new SpeechSynthesis(context, null); | |||
group.addPreference(createSpeakPunctuationPreference(context, engine, R.string.espeak_speak_punctuation)); | |||
group.addPreference(createPreference(context, engine.Rate, "espeak_rate", R.string.setting_default_rate)); | |||
group.addPreference(createPreference(context, engine.Pitch, "espeak_pitch", R.string.setting_default_pitch)); | |||
group.addPreference(createPreference(context, engine.PitchRange, "espeak_pitch_range", R.string.espeak_pitch_range)); | |||
@@ -189,6 +202,8 @@ public class TtsSettingsActivity extends PreferenceActivity { | |||
final SeekBarPreference seekBarPreference = (SeekBarPreference) preference; | |||
String formatter = seekBarPreference.getFormatter(); | |||
summary = String.format(formatter, (String)newValue); | |||
} else { | |||
summary = (String)newValue; | |||
} | |||
preference.setSummary(summary); | |||
} |