diff --git a/android/eSpeakTests/.classpath b/android/eSpeakTests/.classpath new file mode 100644 index 00000000..8c0d9401 --- /dev/null +++ b/android/eSpeakTests/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/eSpeakTests/.project b/android/eSpeakTests/.project new file mode 100644 index 00000000..a3134b73 --- /dev/null +++ b/android/eSpeakTests/.project @@ -0,0 +1,34 @@ + + + eSpeakTests + + + eSpeakActivity + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/android/eSpeakTests/AndroidManifest.xml b/android/eSpeakTests/AndroidManifest.xml new file mode 100644 index 00000000..b4a65001 --- /dev/null +++ b/android/eSpeakTests/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/eSpeakTests/proguard-project.txt b/android/eSpeakTests/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/android/eSpeakTests/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/eSpeakTests/project.properties b/android/eSpeakTests/project.properties new file mode 100644 index 00000000..a3ee5ab6 --- /dev/null +++ b/android/eSpeakTests/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 diff --git a/android/eSpeakTests/res/drawable-hdpi/ic_launcher.png b/android/eSpeakTests/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000..96a442e5 Binary files /dev/null and b/android/eSpeakTests/res/drawable-hdpi/ic_launcher.png differ diff --git a/android/eSpeakTests/res/drawable-ldpi/ic_launcher.png b/android/eSpeakTests/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 00000000..99238729 Binary files /dev/null and b/android/eSpeakTests/res/drawable-ldpi/ic_launcher.png differ diff --git a/android/eSpeakTests/res/drawable-mdpi/ic_launcher.png b/android/eSpeakTests/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..359047df Binary files /dev/null and b/android/eSpeakTests/res/drawable-mdpi/ic_launcher.png differ diff --git a/android/eSpeakTests/res/drawable-xhdpi/ic_launcher.png b/android/eSpeakTests/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..71c6d760 Binary files /dev/null and b/android/eSpeakTests/res/drawable-xhdpi/ic_launcher.png differ diff --git a/android/eSpeakTests/res/values/strings.xml b/android/eSpeakTests/res/values/strings.xml new file mode 100644 index 00000000..2f2fb5b3 --- /dev/null +++ b/android/eSpeakTests/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + ESpeakTestsTest + + \ No newline at end of file diff --git a/android/eSpeakTests/src/com/reecedunn/espeak/test/SpeechSynthesisTest.java b/android/eSpeakTests/src/com/reecedunn/espeak/test/SpeechSynthesisTest.java new file mode 100644 index 00000000..91bb59e6 --- /dev/null +++ b/android/eSpeakTests/src/com/reecedunn/espeak/test/SpeechSynthesisTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2012 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.test; + +import java.util.List; + +import com.reecedunn.espeak.SpeechSynthesis; +import com.reecedunn.espeak.SpeechSynthesis.Voice; + +import android.media.AudioFormat; +import android.test.AndroidTestCase; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class SpeechSynthesisTest extends AndroidTestCase +{ + private SpeechSynthesis.SynthReadyCallback mCallback = new SpeechSynthesis.SynthReadyCallback() + { + @Override + public void onSynthDataReady(byte[] audioData) + { + } + + @Override + public void onSynthDataComplete() + { + } + }; + + private List mVoices = null; + + public List getVoices() + { + if (mVoices == null) + { + SpeechSynthesis synth = new SpeechSynthesis(getContext(), mCallback); + mVoices = synth.getAvailableVoices(); + assertThat(mVoices, is(notNullValue())); + assertThat(mVoices.size(), is(78)); + } + return mVoices; + } + + public Voice getVoice(String name) + { + for (Voice voice : getVoices()) + { + if (voice.name.equals(name)) + { + return voice; + } + } + return null; + } + + public void checkVoice(String name, String identifier, String language, String iso3Language, String country, String iso3Country, String variant, int gender) + { + Voice voice = getVoice(name); + assertThat(voice, is(notNullValue())); + assertThat(voice.name, is(name)); + assertThat(voice.identifier, is(identifier)); + assertThat(voice.age, is(0)); + assertThat(voice.gender, is(gender)); + assertThat(voice.locale.getLanguage(), is(language)); + assertThat(voice.locale.getISO3Language(), is(iso3Language)); + assertThat(voice.locale.getCountry(), is(country)); + assertThat(voice.locale.getISO3Country(), is(iso3Country)); + assertThat(voice.locale.getVariant(), is(variant)); + } + + public void testConstruction() + { + SpeechSynthesis synth = new SpeechSynthesis(getContext(), mCallback); + assertThat(synth.getSampleRate(), is(22050)); + assertThat(synth.getChannelCount(), is(1)); + assertThat(synth.getAudioFormat(), is(AudioFormat.ENCODING_PCM_16BIT)); + assertThat(synth.getBufferSizeInBytes(), is(22050)); + } + + public void testAvailableVoices() + { + // : : : language : country : : + // : name : identifier : 639-1 : 639-2/T : 2 : 3 : variant : gender + checkVoice("af", "af", "af", "afr", "", "", "", SpeechSynthesis.GENDER_MALE); // Afrikaans + checkVoice("ak", "test/ak", "ak", "aka", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Akan + checkVoice("am", "test/am", "am", "amh", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Amharic + checkVoice("az", "test/az", "az", "aze", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Azerbaijani + checkVoice("bg", "test/bg", "bg", "bul", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Bulgarian + checkVoice("bs", "bs", "bs", "bos", "", "", "", SpeechSynthesis.GENDER_MALE); // Bosnian + checkVoice("ca", "ca", "ca", "cat", "", "", "", SpeechSynthesis.GENDER_MALE); // Catalan + checkVoice("cs", "cs", "cs", "ces", "", "", "", SpeechSynthesis.GENDER_MALE); // Czech + checkVoice("cy", "cy", "cy", "cym", "", "", "", SpeechSynthesis.GENDER_MALE); // Welsh + checkVoice("da", "da", "da", "dan", "", "", "", SpeechSynthesis.GENDER_MALE); // Danish + checkVoice("de", "de", "de", "deu", "", "", "", SpeechSynthesis.GENDER_MALE); // German + checkVoice("dv", "test/dv", "dv", "div", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Divehi + checkVoice("el", "el", "el", "ell", "", "", "", SpeechSynthesis.GENDER_MALE); // Greek (Modern) + checkVoice("en", "default", "en", "eng", "", "", "", SpeechSynthesis.GENDER_MALE); // German + checkVoice("en-sc", "en/en-sc", "en", "eng", "GB", "GBR", "scotland", SpeechSynthesis.GENDER_MALE); // English (Scotland) + checkVoice("en-uk", "en/en", "en", "eng", "GB", "GBR", "", SpeechSynthesis.GENDER_MALE); // English (UK) + checkVoice("en-uk-north", "en/en-n", "en", "eng", "GB", "GBR", "north", SpeechSynthesis.GENDER_MALE); // English (Lancashire) + checkVoice("en-uk-rp", "en/en-rp", "en", "eng", "GB", "GBR", "rp", SpeechSynthesis.GENDER_MALE); // English (Received Pronunciation) + checkVoice("en-uk-wmids", "en/en-wm", "en", "eng", "GB", "GBR", "wmids", SpeechSynthesis.GENDER_MALE); // English (West Midlands) + checkVoice("en-us", "en/en-us", "en", "eng", "US", "USA", "", SpeechSynthesis.GENDER_MALE); // English (US) + checkVoice("en-wi", "en/en-wi", "en", "eng", "029", "", "", SpeechSynthesis.GENDER_MALE); // English (Caribbean) + checkVoice("eo", "eo", "eo", "epo", "", "", "", SpeechSynthesis.GENDER_MALE); // Esperanto + checkVoice("es-la", "es-la", "es", "spa", "419", "", "", SpeechSynthesis.GENDER_MALE); // Spanish (Latin America & Caribbean) + checkVoice("et", "et", "et", "est", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Estonian + checkVoice("fi", "fi", "fi", "fin", "", "", "", SpeechSynthesis.GENDER_MALE); // Finnish + checkVoice("fr-fr", "fr", "fr", "fra", "FR", "FRA", "", SpeechSynthesis.GENDER_MALE); // French (France) + checkVoice("ga", "test/ga", "ga", "gle", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Irish + checkVoice("grc", "test/grc", "grc", "grc", "", "", "", SpeechSynthesis.GENDER_MALE); // Greek (Ancient) + checkVoice("hi", "hi", "hi", "hin", "", "", "", SpeechSynthesis.GENDER_MALE); // Hindi + checkVoice("hr", "hr", "hr", "hrv", "", "", "", SpeechSynthesis.GENDER_MALE); // Croatian + checkVoice("ht", "test/ht", "ht", "hat", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Haitian Creole + checkVoice("hu", "hu", "hu", "hun", "", "", "", SpeechSynthesis.GENDER_MALE); // Hungarian + checkVoice("hy", "hy", "hy", "hye", "", "", "", SpeechSynthesis.GENDER_MALE); // Armenian + checkVoice("hy-west", "hy-west", "hy", "hye", "", "", "arevmda", SpeechSynthesis.GENDER_MALE); // Armenian (West) + checkVoice("id", "id", "in", "ind", "", "", "", SpeechSynthesis.GENDER_MALE); // Indonesia + checkVoice("is", "is", "is", "isl", "", "", "", SpeechSynthesis.GENDER_MALE); // Icelandic + checkVoice("it", "it", "it", "ita", "", "", "", SpeechSynthesis.GENDER_MALE); // Italian + checkVoice("jbo", "test/jbo", "jbo", "jbo", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Lojban + checkVoice("ka", "ka", "ka", "kat", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Georgian + checkVoice("kk", "test/kk", "kk", "kaz", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Kazakh + checkVoice("kl", "test/kl", "kl", "kal", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Greenlandic + checkVoice("kn", "kn", "kn", "kan", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Kannada + checkVoice("ko", "test/ko", "ko", "kor", "", "", "", SpeechSynthesis.GENDER_MALE); // Korean + checkVoice("ku", "ku", "ku", "kur", "", "", "", SpeechSynthesis.GENDER_MALE); // Kurdish + checkVoice("la", "la", "la", "lat", "", "", "", SpeechSynthesis.GENDER_MALE); // Latin + checkVoice("lt", "test/lt", "lt", "lit", "", "", "", SpeechSynthesis.GENDER_MALE); // Lithuanian + checkVoice("lv", "lv", "lv", "lav", "", "", "", SpeechSynthesis.GENDER_MALE); // Latvian + checkVoice("ml", "ml", "ml", "mal", "", "", "", SpeechSynthesis.GENDER_MALE); // Malayalam + checkVoice("mt", "test/mt", "mt", "mlt", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Maltese + checkVoice("nci", "test/nci", "nci", "", "", "", "", SpeechSynthesis.GENDER_MALE); // Classical Nahuatl + checkVoice("ne", "test/ne", "ne", "nep", "", "", "", SpeechSynthesis.GENDER_MALE); // Nepali + checkVoice("nl", "nl", "nl", "nld", "", "", "", SpeechSynthesis.GENDER_MALE); // Dutch + checkVoice("no", "no", "no", "nor", "", "", "", SpeechSynthesis.GENDER_MALE); // Norwegian + checkVoice("nso", "test/nso", "nso", "nso", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Sotho (Northern) + checkVoice("pa", "test/pa", "pa", "pan", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Panjabi + checkVoice("pap", "test/pap", "pap", "pap", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Papiamento + checkVoice("prs", "test/prs", "prs", "", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Dari (Afghan Persian) + checkVoice("pt-br", "pt", "pt", "por", "BR", "BRA", "", SpeechSynthesis.GENDER_MALE); // Portuguese (Brazil) + checkVoice("pt-pt", "pt-pt", "pt", "por", "PT", "PRT", "", SpeechSynthesis.GENDER_MALE); // Portuguese (Portugal) + checkVoice("ro", "ro", "ro", "ron", "", "", "", SpeechSynthesis.GENDER_MALE); // Romanian + checkVoice("ru", "ru", "ru", "rus", "", "", "", SpeechSynthesis.GENDER_MALE); // Russian + checkVoice("rw", "test/rw", "rw", "kin", "", "", "", SpeechSynthesis.GENDER_MALE); // Kinyarwanda + checkVoice("si", "test/si", "si", "sin", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Sinhalese + checkVoice("sk", "sk", "sk", "slk", "", "", "", SpeechSynthesis.GENDER_MALE); // Slovak + checkVoice("sl", "test/sl", "sl", "slv", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Slovenian + checkVoice("sq", "sq", "sq", "sqi", "", "", "", SpeechSynthesis.GENDER_MALE); // Albanian + checkVoice("sr", "sr", "sr", "srp", "", "", "", SpeechSynthesis.GENDER_MALE); // Serbian + checkVoice("sv", "sv", "sv", "swe", "", "", "", SpeechSynthesis.GENDER_MALE); // Swedish + checkVoice("ta", "ta", "ta", "tam", "", "", "", SpeechSynthesis.GENDER_MALE); // Tamil + checkVoice("te", "test/te", "te", "tel", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Telugu + checkVoice("tn", "test/tn", "tn", "tsn", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Setswana + checkVoice("tr", "tr", "tr", "tur", "", "", "", SpeechSynthesis.GENDER_MALE); // Turkish + checkVoice("tt", "test/tt", "tt", "tat", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Tatar + checkVoice("ur", "test/ur", "ur", "urd", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Urdu + checkVoice("vi", "vi", "vi", "vie", "", "", "", SpeechSynthesis.GENDER_MALE); // Vietnamese + checkVoice("wo", "test/wo", "wo", "wol", "", "", "", SpeechSynthesis.GENDER_UNSPECIFIED); // Wolof + checkVoice("zh", "zh", "zh", "zho", "", "", "", SpeechSynthesis.GENDER_MALE); // Chinese (Mandarin) + checkVoice("zh-yue", "zh-yue", "yue", "", "", "", "", SpeechSynthesis.GENDER_MALE); // Chinese (Cantonese) + } +} diff --git a/android/src/com/reecedunn/espeak/SpeechSynthesis.java b/android/src/com/reecedunn/espeak/SpeechSynthesis.java index ced93dc7..3cd8d042 100644 --- a/android/src/com/reecedunn/espeak/SpeechSynthesis.java +++ b/android/src/com/reecedunn/espeak/SpeechSynthesis.java @@ -35,6 +35,7 @@ import java.util.Locale; public class SpeechSynthesis { private static final String TAG = SpeechSynthesis.class.getSimpleName(); + public static final int GENDER_UNSPECIFIED = 0; public static final int GENDER_MALE = 1; public static final int GENDER_FEMALE = 2; @@ -206,7 +207,45 @@ public class SpeechSynthesis { this.gender = gender; this.age = age; - locale = new Locale(name); + if (name.equals("en-sc")) { + // 'SC' is not a country code. + locale = new Locale("en", "GB", "scotland"); + } else if (name.equals("en-wi")) { + // 'WI' is not a country code. + locale = new Locale("en", "029"); + } else if (name.equals("es-la")) { + // 'LA' is the country code for Laos, not Latin America. + locale = new Locale("es", "419"); + } else if (name.equals("hy-west")) { + // 'west' is not a country code. + locale = new Locale("hy", "", "arevmda"); + } else if (name.equals("zh-yue")) { + // Android/Java does not support macrolanguages. + locale = new Locale("yue"); + } else { + String[] parts = name.split("-"); + switch (parts.length) { + case 1: // language + locale = new Locale(parts[0]); + break; + case 2: // language-country + if (parts[1].equals("uk")) { + // 'uk' is the language code for Ukranian, not Great Britain. + parts[1] = "GB"; + } + locale = new Locale(parts[0], parts[1]); + break; + case 3: // language-country-variant + if (parts[1].equals("uk")) { + // 'uk' is the language code for Ukranian, not Great Britain. + parts[1] = "GB"; + } + locale = new Locale(parts[0], parts[1], parts[2]); + break; + default: + locale = null; + } + } } /**