LOCAL_PATH:= $(call my-dir) | LOCAL_PATH:= $(call my-dir) | ||||
include $(CLEAR_VARS) | include $(CLEAR_VARS) | ||||
LOCAL_CFLAGS = -std=c++11 | |||||
# ucd-tools wide-character compatibility support: | # ucd-tools wide-character compatibility support: | ||||
UCDTOOLS_SRC_PATH := ../../ucd-tools/src | UCDTOOLS_SRC_PATH := ../../ucd-tools/src |
#include <TtsEngine.h> | #include <TtsEngine.h> | ||||
#include <Log.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 LOG_TAG "eSpeakService" | ||||
#define DEBUG true | #define DEBUG true | ||||
return espeak_GetParameter((espeak_PARAMETER)parameter, (int)current); | 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 | JNIEXPORT jboolean | ||||
JNICALL Java_com_reecedunn_espeak_SpeechSynthesis_nativeSynthesize( | JNICALL Java_com_reecedunn_espeak_SpeechSynthesis_nativeSynthesize( | ||||
JNIEnv *env, jobject object, jstring text, jboolean isSsml) { | JNIEnv *env, jobject object, jstring text, jboolean isSsml) { |
<?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> |
<item>female-old</item> | <item>female-old</item> | ||||
</string-array> | </string-array> | ||||
<string-array name="punctuation_level_values"> | |||||
<item>0</item> | |||||
<item>1</item> | |||||
<item>2</item> | |||||
</string-array> | |||||
</resources> | </resources> |
<string name="resetToDefault">Set to default</string> | <string name="resetToDefault">Set to default</string> | ||||
<string name="espeak_pitch_range">Pitch variation</string> | <string name="espeak_pitch_range">Pitch variation</string> | ||||
<string name="espeak_variant">Voice variant</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> | </resources> |
android:summary="%s" | android:summary="%s" | ||||
android:title="@string/espeak_variant" /> | 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> | </PreferenceScreen> |
/* | |||||
* 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); | |||||
} | |||||
} |
} | } | ||||
} | } | ||||
public void setPunctuationCharacters(String characters) { | |||||
nativeSetPunctuationCharacters(characters); | |||||
} | |||||
/** Don't announce any punctuation characters. */ | /** Don't announce any punctuation characters. */ | ||||
public static int PUNCT_NONE = 0; | |||||
public static final int PUNCT_NONE = 0; | |||||
/** Announce every punctuation character. */ | /** Announce every punctuation character. */ | ||||
public static int PUNCT_ALL = 1; | |||||
public static final int PUNCT_ALL = 1; | |||||
/** Announce some of the punctuation characters. */ | /** Announce some of the punctuation characters. */ | ||||
public static int PUNCT_SOME = 2; | |||||
public static final int PUNCT_SOME = 2; | |||||
public enum UnitType { | public enum UnitType { | ||||
Percentage, | Percentage, | ||||
private native final int nativeGetParameter(int parameter, int current); | 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 nativeSynthesize(String text, boolean isSsml); | ||||
private native final boolean nativeStop(); | private native final boolean nativeStop(); |
mEngine.PitchRange.setValue(settings.getPitchRange()); | mEngine.PitchRange.setValue(settings.getPitchRange()); | ||||
mEngine.Volume.setValue(settings.getVolume()); | mEngine.Volume.setValue(settings.getVolume()); | ||||
mEngine.Punctuation.setValue(settings.getPunctuationLevel()); | mEngine.Punctuation.setValue(settings.getPunctuationLevel()); | ||||
mEngine.setPunctuationCharacters(settings.getPunctuationCharacters()); | |||||
mEngine.synthesize(text, text.startsWith("<speak")); | mEngine.synthesize(text, text.startsWith("<speak")); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
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) { | private static Preference createPreference(Context context, SpeechSynthesis.Parameter parameter, String key, int titleRes) { | ||||
final String title = context.getString(titleRes); | final String title = context.getString(titleRes); | ||||
final int defaultValue = parameter.getDefaultValue(); | final int defaultValue = parameter.getDefaultValue(); | ||||
SpeechSynthesis engine = new SpeechSynthesis(context, null); | 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.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.Pitch, "espeak_pitch", R.string.setting_default_pitch)); | ||||
group.addPreference(createPreference(context, engine.PitchRange, "espeak_pitch_range", R.string.espeak_pitch_range)); | group.addPreference(createPreference(context, engine.PitchRange, "espeak_pitch_range", R.string.espeak_pitch_range)); | ||||
final SeekBarPreference seekBarPreference = (SeekBarPreference) preference; | final SeekBarPreference seekBarPreference = (SeekBarPreference) preference; | ||||
String formatter = seekBarPreference.getFormatter(); | String formatter = seekBarPreference.getFormatter(); | ||||
summary = String.format(formatter, (String)newValue); | summary = String.format(formatter, (String)newValue); | ||||
} else { | |||||
summary = (String)newValue; | |||||
} | } | ||||
preference.setSummary(summary); | preference.setSummary(summary); | ||||
} | } |