DroidFish: German text to speech support.

This commit is contained in:
Peter Osterlund 2016-12-26 00:41:06 +01:00
parent c43e5656d7
commit ecb078c205
4 changed files with 280 additions and 87 deletions

View File

@ -704,10 +704,12 @@ you are not actively using the program.\
<item>off</item>
<item>sound</item>
<item>speech_en</item>
</string-array>
<item>speech_de</item>
</string-array>
<string-array name="move_announce_type_texts">
<item>Off</item>
<item>Play sound</item>
<item>English Speech</item>
</string-array>
<item>German Speech</item>
</string-array>
</resources>

View File

@ -3776,7 +3776,7 @@ public class DroidFish extends Activity
/** Initialize text to speech if enabled in settings. */
private void initSpeech() {
if (moveAnnounceType.startsWith("speech_"))
speech.initialize(this);
speech.initialize(this, moveAnnounceType.substring(7));
}
@Override
@ -3793,7 +3793,7 @@ public class DroidFish extends Activity
}
}
} else if (moveAnnounceType.startsWith("speech_")) {
speech.say(pos, move, moveAnnounceType.substring(7));
speech.say(pos, move);
}
if (vibrateEnabled) {
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

View File

@ -33,11 +33,33 @@ import android.widget.Toast;
/** Handles text to speech translation. */
public class Speech {
private TextToSpeech tts;
boolean initialized = false;
boolean supported = false;
String toSpeak = null;
private boolean initialized = false;
private String toSpeak = null;
public void initialize(final Context context) {
public enum Language {
EN, // English
DE, // German
NONE; // Not supported
public static Language fromString(String langStr) {
if ("en".equals(langStr))
return EN;
if ("de".equals(langStr))
return DE;
return NONE;
}
}
private Language lang;
/** Initialize the text to speech engine for a given language. */
public void initialize(final Context context, final String langStr) {
Language newLang = Language.fromString(langStr);
if (newLang != lang)
shutdown();
final Locale loc = getLocale(newLang);
if (loc == null)
initialized = true;
if (initialized)
return;
tts = new TextToSpeech(context, new OnInitListener() {
@ -46,12 +68,12 @@ public class Speech {
initialized = true;
int toast = -1;
if (status == TextToSpeech.SUCCESS) {
int code = tts.setLanguage(Locale.US);
int code = tts.setLanguage(loc);
switch (code) {
case TextToSpeech.LANG_AVAILABLE:
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
supported = true;
lang = Language.fromString(langStr);
say(toSpeak);
break;
case TextToSpeech.LANG_MISSING_DATA:
@ -75,7 +97,7 @@ public class Speech {
@SuppressWarnings("deprecation")
public void say(String text) {
if (initialized) {
if (supported && text != null)
if (lang != Language.NONE && text != null)
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
toSpeak = null;
} else {
@ -95,22 +117,22 @@ public class Speech {
if (tts != null) {
tts.shutdown();
tts = null;
lang = Language.NONE;
initialized = false;
supported = false;
}
}
/** Convert move "move" in position "pos" to a sentence and speak it. */
public void say(Position pos, Move move, String langStr) {
String s = moveToText(pos, move, langStr);
public void say(Position pos, Move move) {
String s = moveToText(pos, move, lang);
// System.out.printf("%.3f Speech.say(): %s\n", System.currentTimeMillis() * 1e-3, s);
if (!s.isEmpty())
say(s);
}
/** Convert move "move" in position "pos" to a sentence that can be spoken. */
public static String moveToText(Position pos, Move move, String langStr) {
if (move == null || !langStr.equals("en"))
public static String moveToText(Position pos, Move move, Language lang) {
if (move == null)
return "";
String moveStr = TextIO.moveToString(pos, move, false, false);
@ -120,12 +142,15 @@ public class Speech {
boolean check = moveStr.endsWith("+");
boolean checkMate = moveStr.endsWith("#");
boolean castle = false;
boolean enPassant = false;
if (piece == Piece.WPAWN && !capture) {
int fx = Position.getX(move.from);
int tx = Position.getX(move.to);
if (fx != tx)
if (fx != tx) {
capture = true; // En passant
enPassant = true;
}
}
StringBuilder sentence = new StringBuilder();
@ -134,10 +159,10 @@ public class Speech {
int fx = Position.getX(move.from);
int tx = Position.getX(move.to);
if (fx == 4 && tx == 6) {
sentence.append("Short castle");
addWord(sentence, castleToString(true, lang));
castle = true;
} else if (fx == 4 && (tx == 2)) {
sentence.append("Long castle");
addWord(sentence, castleToString(false, lang));
castle = true;
}
}
@ -145,65 +170,178 @@ public class Speech {
if (!castle) {
boolean pawnMove = piece == Piece.WPAWN;
if (!pawnMove)
sentence.append(pieceName(piece)).append(' ');
addWord(sentence, pieceName(piece, lang));
if (capture) {
int i = moveStr.indexOf("x");
String from = moveStr.substring(pawnMove ? 0 : 1, i);
if (!from.isEmpty())
sentence.append(getFromWord(from)).append(' ');
addWord(sentence, fromToString(from, lang));
String to = moveStr.substring(i + 1, i + 3);
sentence.append(to.startsWith("e") ? "take " : "takes ");
sentence.append(to).append(' ');
addWord(sentence, captureToString(to, lang));
addWord(sentence, toToString(to, lang));
if (enPassant)
addWord(sentence, epToString(lang));
} else {
int nSkip = (promotion ? 1 : 0) + ((check | checkMate) ? 1 : 0);
int i = moveStr.length() - nSkip;
String from = moveStr.substring(pawnMove ? 0 : 1, i - 2);
if (!from.isEmpty())
sentence.append(from).append(' ');
addWord(sentence, fromToString(from, lang));
String to = moveStr.substring(i - 2, i);
sentence.append(to).append(' ');
addWord(sentence, toToString(to, lang));
}
if (promotion)
sentence.append(pieceName(move.promoteTo)).append(' ');
addWord(sentence, promToString(move.promoteTo, lang));
}
if (checkMate) {
removeLastSpace(sentence);
sentence.append(". Check mate!");
addWord(sentence, checkMateToString(lang));
} else if (check) {
removeLastSpace(sentence);
sentence.append(". Check!");
addWord(sentence, checkToString(lang));
}
return sentence.toString().trim();
}
/** Return the locale corresponding to a language string,
* or null if language not supported. */
private static Locale getLocale(Language lang) {
switch (lang) {
case EN:
return Locale.US;
case DE:
return Locale.GERMAN;
case NONE:
return null;
}
throw new IllegalArgumentException();
}
/** Add zero or more words to the string builder.
* If anything was added, an extra space is also added at the end. */
private static void addWord(StringBuilder sb, String words) {
if (!words.isEmpty())
sb.append(words).append(' ');
}
/** Get the name of a non-pawn piece. Return empty string if no such piece. */
private static String pieceName(int piece) {
private static String pieceName(int piece, Language lang) {
piece = Piece.makeWhite(piece);
switch (piece) {
case Piece.WKING: return "King";
case Piece.WQUEEN: return "Queen";
case Piece.WROOK: return "Rook";
case Piece.WBISHOP: return "Bishop";
case Piece.WKNIGHT: return "Knight";
default: return "";
}
switch (lang) {
case EN:
switch (piece) {
case Piece.WKING: return "King";
case Piece.WQUEEN: return "Queen";
case Piece.WROOK: return "Rook";
case Piece.WBISHOP: return "Bishop";
case Piece.WKNIGHT: return "Knight";
default: return "";
}
case DE:
switch (piece) {
case Piece.WKING: return "König";
case Piece.WQUEEN: return "Dame";
case Piece.WROOK: return "Turm";
case Piece.WBISHOP: return "Läufer";
case Piece.WKNIGHT: return "Springer";
default: return "";
}
case NONE:
return "";
}
throw new IllegalArgumentException();
}
/** Transform a "from" file or file+rank to a word. */
private static String getFromWord(String from) {
if ("a".equals(from))
return "ae";
return from;
private static String fromToString(String from, Language lang) {
switch (lang) {
case EN:
if ("a".equals(from))
return "ae";
return from;
case DE:
return from;
case NONE:
return "";
}
throw new IllegalArgumentException();
}
/** If the last character in the StringBuilder is a space, remove it. */
private static void removeLastSpace(StringBuilder sb) {
int len = sb.length();
if (len > 0 && sb.charAt(len - 1) == ' ')
sb.setLength(len - 1);
private static String toToString(String to, Language lang) {
return to;
}
private static String captureToString(String to, Language lang) {
switch (lang) {
case EN:
return to.startsWith("e") ? "take" : "takes";
case DE:
return "schlägt";
case NONE:
return "";
}
throw new IllegalArgumentException();
}
private static String castleToString(boolean kingSide, Language lang) {
switch (lang) {
case EN:
return kingSide ? "Short castle" : "Long castle";
case DE:
return kingSide ? "Kleine Rochade" : "Große Rochade";
case NONE:
return "";
}
throw new IllegalArgumentException();
}
private static String epToString(Language lang) {
switch (lang) {
case EN:
return "";
case DE:
return "en passant";
case NONE:
return "";
}
throw new IllegalArgumentException();
}
private static String promToString(int piece, Language lang) {
String pn = pieceName(piece, lang);
switch (lang) {
case EN:
return pn;
case DE:
return "Umwandlung zu " + pn;
case NONE:
return "";
}
throw new IllegalArgumentException();
}
private static String checkToString(Language lang) {
switch (lang) {
case EN:
return "check!";
case DE:
return "Schach!";
case NONE:
return "";
}
throw new IllegalArgumentException();
}
private static String checkMateToString(Language lang) {
switch (lang) {
case EN:
return "check mate!";
case DE:
return "Schach matt!";
case NONE:
return "";
}
throw new IllegalArgumentException();
}
}

View File

@ -26,130 +26,183 @@ public class SpeechTest extends TestCase {
public SpeechTest() {
}
private String[] moveToText(Position pos, Move move, String[] langStrs) {
String[] ret = new String[langStrs.length];
for (int i = 0; i < langStrs.length; i++) {
Speech.Language lang = Speech.Language.fromString(langStrs[i]);
assertTrue(lang != null);
ret[i] = Speech.moveToText(pos, move, lang);
}
return ret;
}
private static void assertEquals(String[] expected, String[] actual) {
assertEquals(expected.length, actual.length);
for (int i = 0; i < expected.length; i++)
assertEquals(expected[i], actual[i]);
}
public void testEnglish() {
String lang = "en";
String[] lang = {"en", "de"};
{
Game game = new Game(null, new TimeControlData());
Pair<Boolean,Move> res = game.processString("e4");
assertEquals("e4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"e4", "e4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("d5");
assertEquals("d5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"d5", "d5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("exd5");
assertEquals("e takes d5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"e takes d5", "e schlägt d5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Qxd5");
assertEquals("Queen takes d5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Queen takes d5", "Dame schlägt d5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Ne2");
assertEquals("Knight e2", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Knight e2", "Springer e2"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Nf6");
assertEquals("Knight f6", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Knight f6", "Springer f6"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Nbc3");
assertEquals("Knight b c3", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Knight b c3", "Springer b c3"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("e5");
assertEquals("e5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"e5", "e5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("b4");
assertEquals("b4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"b4", "b4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("a5");
assertEquals("a5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"a5", "a5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("a3");
assertEquals("a3", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"a3", "a3"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("axb4");
assertEquals("ae takes b4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"ae takes b4", "a schlägt b4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("axb4");
assertEquals("ae takes b4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"ae takes b4", "a schlägt b4"},
moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
Pair<Boolean,Move> res = game.processString("d4");
assertEquals("d4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"d4", "d4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("e5");
assertEquals("e5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"e5", "e5"}, moveToText(game.prevPos(), res.second, lang));
res = game.processString("dxe5");
assertEquals("d take e5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"d take e5", "d schlägt e5"}, moveToText(game.prevPos(), res.second, lang));
res = game.processString("f6");
assertEquals("f6", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"f6", "f6"}, moveToText(game.prevPos(), res.second, lang));
res = game.processString("exf6");
assertEquals("e takes f6", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"e takes f6", "e schlägt f6"}, moveToText(game.prevPos(), res.second, lang));
res = game.processString("Bb4");
assertEquals("Bishop b4. Check!", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Bishop b4 check!", "Läufer b4 Schach!"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("c3");
assertEquals("c3", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"c3", "c3"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Ne7");
assertEquals("Knight e7", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Knight e7", "Springer e7"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("cxb4");
assertEquals("c takes b4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"c takes b4", "c schlägt b4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("O-O");
assertEquals("Short castle", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Short castle", "Kleine Rochade"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("fxg7");
assertEquals("f takes g7", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"f takes g7", "f schlägt g7"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("h6");
assertEquals("h6", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"h6", "h6"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("gxf8Q+");
assertEquals("g takes f8 Queen. Check!", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"g takes f8 Queen check!", "g schlägt f8 Umwandlung zu Dame Schach!"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Kxf8");
assertEquals("King takes f8", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"King takes f8", "König schlägt f8"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("b5");
assertEquals("b5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"b5", "b5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("a5");
assertEquals("a5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"a5", "a5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("bxa6");
assertEquals("b takes a6", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"b takes a6", "b schlägt a6 en passant"},
moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
Pair<Boolean,Move> res = game.processString("f4");
assertEquals("f4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"f4", "f4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("e5");
assertEquals("e5", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"e5", "e5"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("g4");
assertEquals("g4", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"g4", "g4"},
moveToText(game.prevPos(), res.second, lang));
res = game.processString("Qh4");
assertEquals("Queen h4. Check mate!", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Queen h4 check mate!", "Dame h4 Schach matt!"},
moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
playMoves(game, "d4 d5 Nc3 Nc6 Bf4 Bf5 Qd2 Qd7");
Pair<Boolean,Move> res = game.processString("O-O-O");
assertEquals("Long castle", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Long castle", "Große Rochade"},
moveToText(game.prevPos(), res.second, lang));
playMoves(game, "Nxd4 Nxd5 Qxd5 Qxd4 Qxd4 Nf3 Qxd1 Kxd1");
res = game.processString("O-O-O");
assertEquals("Long castle. Check!", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Long castle check!", "Große Rochade Schach!"},
moveToText(game.prevPos(), res.second, lang));
playMoves(game, "Kc1");
res = game.processString("Rd7");
assertEquals(new String[]{"Rook d7", "Turm d7"},
moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
playMoves(game, "e4 e5 h3 Bb4 Ne2 Bc3");
Pair<Boolean,Move> res = game.processString("Nexc3");
assertEquals("Knight e takes c3", Speech.moveToText(game.prevPos(), res.second, lang));
assertEquals(new String[]{"Knight e takes c3", "Springer e schlägt c3"},
moveToText(game.prevPos(), res.second, lang));
}
}