1b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath/*
2b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * Copyright (C) 2011 The Android Open Source Project
3b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath *
4b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * use this file except in compliance with the License. You may obtain a copy of
6b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * the License at
7b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath *
8b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * http://www.apache.org/licenses/LICENSE-2.0
9b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath *
10b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * Unless required by applicable law or agreed to in writing, software
11b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * License for the specific language governing permissions and limitations under
14b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * the License.
15b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath */
16b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathpackage com.example.android.ttsengine;
17b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
18b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.content.Context;
19b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.content.SharedPreferences;
20b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.media.AudioFormat;
21b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.speech.tts.SynthesisCallback;
22b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.speech.tts.SynthesisRequest;
23b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.speech.tts.TextToSpeech;
24b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.speech.tts.TextToSpeechService;
25b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport android.util.Log;
26b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
27b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.io.BufferedReader;
28b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.io.IOException;
29b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.io.InputStream;
30b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.io.InputStreamReader;
31b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.nio.ByteBuffer;
32b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.nio.ByteOrder;
33b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.util.HashMap;
34b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathimport java.util.Map;
35b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
36b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath/**
37b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * A text to speech engine that generates "speech" that a robot might understand.
38b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * The engine supports two different "languages", each with their own frequency
39b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * mappings.
40b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath *
41b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * It exercises all aspects of the Text to speech engine API
42b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath * {@link android.speech.tts.TextToSpeechService}.
43b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath */
44b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamathpublic class RobotSpeakTtsService extends TextToSpeechService {
45b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private static final String TAG = "ExampleTtsService";
46b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
47b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    /*
48b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * This is the sampling rate of our output audio. This engine outputs
49b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * audio at 16khz 16bits per sample PCM audio.
50b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     */
51b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private static final int SAMPLING_RATE_HZ = 16000;
52b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
53b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    /*
54b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * We multiply by a factor of two since each sample contains 16 bits (2 bytes).
55b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     */
56b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private final byte[] mAudioBuffer = new byte[SAMPLING_RATE_HZ * 2];
57b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
58b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private Map<Character, Integer> mFrequenciesMap;
59b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private volatile String[] mCurrentLanguage = null;
60b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private volatile boolean mStopRequested = false;
61b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private SharedPreferences mSharedPrefs = null;
62b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
63b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
64b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    public void onCreate() {
65b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        super.onCreate();
66b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        mSharedPrefs = getSharedPreferences(GeneralSettingsFragment.SHARED_PREFS_NAME,
67b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                Context.MODE_PRIVATE);
68b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // We load the default language when we start up. This isn't strictly
69b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // required though, it can always be loaded lazily on the first call to
70b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // onLoadLanguage or onSynthesizeText. This a tradeoff between memory usage
71b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // and the latency of the first call.
72b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        onLoadLanguage("eng", "usa", "");
73b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
74b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
75b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
76b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    public void onDestroy() {
77b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        super.onDestroy();
78b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
79b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
80b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
81b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    protected String[] onGetLanguage() {
82b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // Note that mCurrentLanguage is volatile because this can be called from
83b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // multiple threads.
84b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        return mCurrentLanguage;
85b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
86b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
87b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
88b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    protected int onIsLanguageAvailable(String lang, String country, String variant) {
89b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // The robot speak synthesizer supports only english.
90b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if ("eng".equals(lang)) {
91b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // We support two specific robot languages, the british robot language
92b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // and the american robot language.
93b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            if ("USA".equals(country) || "GBR".equals(country)) {
94b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                // If the engine supported a specific variant, we would have
95b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                // something like.
96b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                //
97b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                // if ("android".equals(variant)) {
98b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                //     return TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
99b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                // }
100b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                return TextToSpeech.LANG_COUNTRY_AVAILABLE;
101b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            }
102b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
103b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // We support the language, but not the country.
104b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return TextToSpeech.LANG_AVAILABLE;
105b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
106b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
107b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        return TextToSpeech.LANG_NOT_SUPPORTED;
108b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
109b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
110b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    /*
111b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * Note that this method is synchronized, as is onSynthesizeText because
112b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * onLoadLanguage can be called from multiple threads (while onSynthesizeText
113b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * is always called from a single thread only).
114b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     */
115b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
116b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    protected synchronized int onLoadLanguage(String lang, String country, String variant) {
117b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        final int isLanguageAvailable = onIsLanguageAvailable(lang, country, variant);
118b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
119b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (isLanguageAvailable == TextToSpeech.LANG_NOT_SUPPORTED) {
120b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return isLanguageAvailable;
121b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
122b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
123b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        String loadCountry = country;
124b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (isLanguageAvailable == TextToSpeech.LANG_AVAILABLE) {
125b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            loadCountry = "USA";
126b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
127b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
128b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // If we've already loaded the requested language, we can return early.
129b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (mCurrentLanguage != null) {
130b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            if (mCurrentLanguage[0].equals(lang) && mCurrentLanguage[1].equals(country)) {
131b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                return isLanguageAvailable;
132b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            }
133b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
134b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
135b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        Map<Character, Integer> newFrequenciesMap = null;
136b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        try {
137b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            InputStream file = getAssets().open(lang + "-" + loadCountry + ".freq");
138b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            newFrequenciesMap = buildFrequencyMap(file);
139b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            file.close();
140b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        } catch (IOException e) {
141b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            Log.e(TAG, "Error loading data for : " + lang + "-" + country);
142b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
143b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
144b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        mFrequenciesMap = newFrequenciesMap;
145b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        mCurrentLanguage = new String[] { lang, loadCountry, ""};
146b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
147b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        return isLanguageAvailable;
148b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
149b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
150b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
151b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    protected void onStop() {
152b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        mStopRequested = true;
153b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
154b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
155b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    @Override
156b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    protected synchronized void onSynthesizeText(SynthesisRequest request,
157b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            SynthesisCallback callback) {
158b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // Note that we call onLoadLanguage here since there is no guarantee
159b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // that there would have been a prior call to this function.
160b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        int load = onLoadLanguage(request.getLanguage(), request.getCountry(),
161b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                request.getVariant());
162b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
163b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // We might get requests for a language we don't support - in which case
164b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // we error out early before wasting too much time.
165b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (load == TextToSpeech.LANG_NOT_SUPPORTED) {
166b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            callback.error();
167b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return;
168b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
169b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
170b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // At this point, we have loaded the language we need for synthesis and
171b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // it is guaranteed that we support it so we proceed with synthesis.
172b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
173b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // We denote that we are ready to start sending audio across to the
174b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // framework. We use a fixed sampling rate (16khz), and send data across
175b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // in 16bit PCM mono.
176b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        callback.start(SAMPLING_RATE_HZ,
177b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                AudioFormat.ENCODING_PCM_16BIT, 1 /* Number of channels. */);
178b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
179b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // We then scan through each character of the request string and
180b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // generate audio for it.
181b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        final String text = request.getText().toLowerCase();
182b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        for (int i = 0; i < text.length(); ++i) {
183b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            char value = normalize(text.charAt(i));
184b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // It is crucial to call either of callback.error() or callback.done() to ensure
185b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // that audio / other resources are released as soon as possible.
186b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            if (!generateOneSecondOfAudio(value, callback)) {
187b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                callback.error();
188b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                return;
189b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            }
190b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
191b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
192b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // Alright, we're done with our synthesis - yay!
193b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        callback.done();
194b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
195b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
196b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    /*
197b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * Normalizes a given character to the range 'a' - 'z' (inclusive). Our
198b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     * frequency mappings contain frequencies for each of these characters.
199b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath     */
200b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private static char normalize(char input) {
201b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (input == ' ') {
202b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return input;
203b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
204b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
205b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (input < 'a') {
206b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return 'a';
207b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
208b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (input > 'z') {
209b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return 'z';
210b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
211b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
212b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        return input;
213b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
214b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
215b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private Map<Character, Integer> buildFrequencyMap(InputStream is) throws IOException {
216b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        BufferedReader br = new BufferedReader(new InputStreamReader(is));
217b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        String line = null;
218b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        Map<Character, Integer> map = new HashMap<Character, Integer>();
219b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        try {
220b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            while ((line = br.readLine()) != null) {
221b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                String[] parts = line.split(":");
222b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                if (parts.length != 2) {
223b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                    throw new IOException("Invalid line encountered: " + line);
224b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                }
225b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                map.put(parts[0].charAt(0), Integer.parseInt(parts[1]));
226b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            }
227b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            map.put(' ', 0);
228b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return map;
229b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        } finally {
230b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            is.close();
231b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
232b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
233b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
234b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private boolean generateOneSecondOfAudio(char alphabet, SynthesisCallback cb) {
235b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        ByteBuffer buffer = ByteBuffer.wrap(mAudioBuffer).order(ByteOrder.LITTLE_ENDIAN);
236b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
237b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // Someone called onStop, end the current synthesis and return.
238b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // The mStopRequested variable will be reset at the beginning of the
239b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // next synthesis.
240b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        //
241b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // In general, a call to onStop( ) should make a best effort attempt
242b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // to stop all processing for the *current* onSynthesizeText request (if
243b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // one is active).
244b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (mStopRequested) {
245b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return false;
246b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
247b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
248b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
249b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (mFrequenciesMap == null || !mFrequenciesMap.containsKey(alphabet)) {
250b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            return false;
251b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
252b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
253b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        final int frequency = mFrequenciesMap.get(alphabet);
254b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
255b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        if (frequency > 0) {
256b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // This is the wavelength in samples. The frequency is chosen so that the
257b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // waveLength is always a multiple of two and frequency divides the
258b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // SAMPLING_RATE exactly.
259b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            final int waveLength = SAMPLING_RATE_HZ / frequency;
260b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            final int times = SAMPLING_RATE_HZ / waveLength;
261b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
262b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            for (int j = 0; j < times; ++j) {
263b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                // For a square curve, half of the values will be at Short.MIN_VALUE
264b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                // and the other half will be Short.MAX_VALUE.
265b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                for (int i = 0; i < waveLength / 2; ++i) {
266b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                    buffer.putShort((short)(getAmplitude() * -1));
267b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                }
268b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                for (int i = 0; i < waveLength / 2; ++i) {
269b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                    buffer.putShort(getAmplitude());
270b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                }
271b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            }
272b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        } else {
273b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            // Play a second of silence.
274b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            for (int i = 0; i < mAudioBuffer.length / 2; ++i) {
275b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath                buffer.putShort((short) 0);
276b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            }
277b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
278b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
279b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        // Get the maximum allowed size of data we can send across in audioAvailable.
280b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        final int maxBufferSize = cb.getMaxBufferSize();
281b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        int offset = 0;
282b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        while (offset < mAudioBuffer.length) {
283b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            int bytesToWrite = Math.min(maxBufferSize, mAudioBuffer.length - offset);
284b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            cb.audioAvailable(mAudioBuffer, offset, bytesToWrite);
285b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath            offset += bytesToWrite;
286b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        }
287b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        return true;
288b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
289b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath
290b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    private short getAmplitude() {
291b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        boolean whisper = mSharedPrefs.getBoolean(GeneralSettingsFragment.WHISPER_KEY, false);
292b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath        return (short) (whisper ? 2048 : 8192);
293b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath    }
294b688761bab0578d6253c93c47ed4a1c1d159407fNarayan Kamath}
295