ORSP is wondering if you would be interested in presenting at our Forum this spring. This is an opportunity for faculty or staff to present on their ongoing research/scholarly/creative activity to a broad segment of the university community. Presentations are 20 minutes to half an hour followed by discussion and are geared to an educated lay audience.
My office of research has been very nice to me, so I consented. What follows is a draft of my presentation.
How much time do you suppose the average American spends reading each day? How about watching television? [Based on the answers, we university folks are either really optimistic (and out of touch), or we are very jaded (and out of touch). Perhaps the guesses are spot on, in which case we are well-educated.] The National Endowment for the Arts said in 2007 that we spend 7 minutes reading and 2 hours watching television each day. Is this bad? I’m hesitant to put such a label on it. Perhaps a more meaningful question would be, “Would I let my three boys be average?” No. I value the active process of reading, and I fear the passive enslavement of television. Bicycling and driving present the same dilemma to me. I want to be in control. Not the machine.
You may think it funny that a computer scientist is saying this. I should love the proliferation of technology, right? But I say it’s perfectly consistent. A computer scientist’s daily task is to make machines do things. When these roles reverse, I go from programmer to programmed. At the same time, I really do like technology, especially animation and 3-D graphics. I love visually exploring imagined worlds and seeing lifeforms that will never walk this Earth. I play video games and enjoy Pixar’s works.
How can we navigate the tension between these two worlds? Well, books and technology are not exclusive. Computers have been able to put words on the screen for a good long time now. Can’t we somehow mix the reading in with the visual stimuli of animation?
A couple of years ago my brother babysat my oldest son and brought an iPad storybook of one of Dr. Seuss’s works. I read it a bit with my son, but reading isn’t the best word to describe what we did. We mashed it. We made things dance. We made things fly. We lifted up rocks and found things hiding. I had a similar experience with this app called Four Seasons, which I’ll share with you. [demo] A lot of digital storybooks follow a similar pattern. The “bookiness” is minimized and the “gaminess” is elevated. Certainly these apps have their merits, but I’m not really reading. Technology can do better than this. At least, I think it can.
Most of our mobile devices support speech recognition. The microphone picks up the pressure waves that hit it and we can attempt to reconstruct the words that produced those waves. So last year, a computer science student named Jonathan, a graphic design student named Katie, and I named Chris tried to build a book that listened to us read.
Here’s the result, a story that Katie wrote and animated:
The whole idea is that we have flashy stuff, but it doesn’t play until the words are read and recognized. I think this turned out pretty well for our first go, but arriving at it was a painful process. We first used Android’s speech recognition capabilities. The process behind speech recognition is this:
That this can work even a little bit is a great score for computer science. The hitch we ran into is that this simply does not work for interactive reading. Sending out the audio and waiting for the results really makes giving quick feedback difficult.

Timings to get different maximum numbers of results for the phrase “As an interactive experience, Amnesia quickly runs into difficulties.”
The effect we were going for was like the little bouncing ball that followed the words on old sing-alongs:
With delays of up to four seconds, the bouncing ball isn’t feasible. Another problem is that the results tend to be a little wonky. Google makes no assumptions about what we are trying to say, apart from its statistical analyses of real speech and text. For example, when I tried to recognize the sentence “David Bowie has a pet dragon,” I got these results:
The good news is that Jonathan discovered a library from Carnegie-Mellon called PocketSphinx. It performs speech recognition directly on our mobile device—no going out to the cloud and waiting for results. Despite the project looking like (and being) the work of a bunch of graduate students, we were able to find one little scrap of documentation so that we could get it up and running. One of the nice things about this library is that we can feed it a lexicon of words that actually occur in the story. It’ll only match those, instead of trying to match words from the entire English language.
The way Digger and Tell works is that the phone is doing many things at once. It’s playing the movie that Katie built independently of us. And it’s listening for the reader’s voice. When the voice is matched, the listener tells the movie player to go. A separate thread of execution is waiting for the next narration point—and it’ll tell the movie player to stop. Managing synchronization between these three threads is not perfect, and sometimes the movie player will charge on through the point it needs to stop. It is frustrating.
This year we decided to try out a real game engine called Unity 3D, which was built to do 3-D interaction and animation. It’s been a lot more fun working with this tool. I was able to put this little proof-of-concept together in a week’s time. It’s called The Unseen, and it’s a little story about hypocrisy and blindness. The trees were a bad idea; they really slow things down on my poor little phone.
As you can see, I couldn’t really get rid of the trees without changing the story.
This year we’ve also looked at supporting other languages, which works incredibly well thanks to the fact that mobile devices were designed from the get-go to change languages. The largest problem by far is getting good results from the speech recognition system. Even with PocketSphinx matching only words that occur in the story, we get back some terrible matches, especially if you sneeze or cough. Currently we are exploring modeling the narration not just as a collection of independent words, but as a miniature grammar, where word order is considered.
As we continue to fight this battle of trying to make books listen to us, we come full force with the problems that every computer scientist faces:
Can we do it? Can we make books listen? Probably. I think we just need to read up on it some more.
]]>Applying toon material to terrain took some searching, as the terrain shader isn’t tweakable via the GUI. The simplest solution I found was to open up the toon material, click Edit to adjust the shader code, and change the first line to
Shader "Hidden/TerrainEngine/Splatmap/Lightmap-FirstPass" {
I don’t know how good of an idea it was to do this, but it worked. The terrain was cel-shaded after I saved the edits. Here’s a short walkthrough:
A playable version exists too.
]]>Today I looked into going in reverse: can I call Unity methods from Android? It turns out to be pretty simple. I first made a little controller script that I attached to a Sphere game object named Ball:
using UnityEngine;
using System.Collections;
public class BallController : MonoBehaviour {
private const float JUMP = 1.0f;
public void Left() {
transform.Translate(-JUMP, 0.0f, 0.0f);
}
void Right() {
transform.Translate(JUMP, 0.0f, 0.0f);
}
void Up() {
transform.Translate(0.0f, JUMP, 0.0f);
}
void Down() {
transform.Translate(0.0f, -JUMP, 0.0f);
}
}
In my custom Android Activity, I can invoke one of these methods like so:
UnityPlayer.UnitySendMessage("Ball", "Left", "");
The first parameter is the game object whose method I want to call. The second is the method I want to call on that game object. The last parameter in that call is for parameters to the method. There are none for Left, so I left it blank. That’s all there was to it.
]]>The resulting .class file(s) should be compressed into a .jar file and placed in the Assets->Plugins->Android folder. Since the manifest dictates which activity to launch it is also necessary to create a new AndroidManifest.xml. The AndroidManifest.xml file should also be placed in the Assets->Plugins->Android folder.
It’s less clear how to package up resources, assets, and libs, but through forum-scrounging and trial and error, I’ve learned that I can simply mirror res, assets, and libs in Assets/Plugins/Android. With those in place, Unity will merge its own resources and other files with mine.
One drawback to this merge is that my R.class file in my *.jar file is not brought over. (Even if I include it, Unity appears to ignore it.) Instead, a brand new R class will be generated. That means my R.*.* identifiers will not work. The workaround is to query for the IDs dynamically, like so:
int textViewID = getResources().getIdentifier("caption", "id", getPackageName());
TextView textView = (TextView) findViewById(textViewID);
// I'd rather do it this way:
// TextView textView = (TextView) findViewById(R.id.caption);
The code above assumes I’m in my custom Activity class.
One of the resources I wanted to bring over was a custom overlay that dropped on top of the Unity game screen. I created a simple FrameLayout with a ToggleButton on it:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ToggleButton
android:id="@+id/toggleButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom" />
</FrameLayout>
Getting this to appear on top of the Unity game screen turned out to be pretty easy. I inflate the XML (overlay.xml) and addContentView in my onCreate method:
int layoutID = getResources().getIdentifier("overlay", "layout", getPackageName());
int buttonID = getResources().getIdentifier("toggleButton", "id", getPackageName());
FrameLayout layout = (FrameLayout) LayoutInflater.from(this).inflate(layoutID, null);
button = (ToggleButton) layout.findViewById(buttonID);
addContentView(layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
The ToggleButton appeared beautifully at the bottom of the screen. It toggled and I rejoiced.
Next up I wanted to draw custom strings depending on the locale. I added the following to res/values/strings.xml:
<string name="line0">ay</string>
<string name="line1">bee</string>
<string name="line2">cee</string>
<string name="line3">dee</string>
I made Spanish versions in res/values-es/strings.xml:
<string name="line0">ah</string>
<string name="line1">bay</string>
<string name="line2">say</string>
<string name="line3">day</string>
Pulling them out sequentially on toggles was pretty straightforward. Here’s the complete Activity:
public class MainActivity extends UnityPlayerActivity {
private int n;
private ToggleButton button;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int layoutID = getResources().getIdentifier("overlay", "layout", getPackageName());
int buttonID = getResources().getIdentifier("toggleButton", "id", getPackageName());
FrameLayout layout = (FrameLayout) LayoutInflater.from(this).inflate(layoutID, null);
button = (ToggleButton) layout.findViewById(buttonID);
addContentView(layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
button.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
Toast.makeText(MainActivity.this, "Checked: " + isChecked + " -> " + getNext(), Toast.LENGTH_SHORT).show();
}
});
n = 0;
}
public String get(int n) {
return getString(getResources().getIdentifier("line" + n, "string", getPackageName()));
}
public String getNext() {
String s = get(n);
n++;
if (n > 3) {
n = 0;
}
return s;
}
}
Switching locales dynamically works just fine. I start reading from the other locale’s resource directory on the next toggle after a locale switch.
]]><project default="build">
<!-- Set this to your top-level Unity project directory! -->
<property
name="project.dir"
value="D:/rezcycle" />
<!-- All Android files will be placed inside Assets/Plugins/Android. No
need to touch this. -->
<property
name="assets.dir"
value="${project.dir}/Assets/Plugins/Android" />
<target name="build">
<!-- Make the assets directory in our project. -->
<mkdir dir="${assets.dir}" />
<!-- Copy over the manifest. -->
<copy
file="AndroidManifest.xml"
todir="${assets.dir}" />
<!-- Copy over the raw files in res/ and assets/. -->
<copy todir="${assets.dir}">
<fileset
dir="."
includes="res/**" />
<fileset
dir="."
includes="assets/**" />
</copy>
<!-- Produce a JAR file of all your pre-compiled classes. Don't bring
R stuff. -->
<jar destfile="${assets.dir}/custom_android.jar">
<fileset
dir="bin/classes"
excludes="**/R.class,**/R$*.class" />
</jar>
</target>
</project>
I drop this in to my top-level Android project directory in Eclipse, naming it build.xml. Eclipse knows how to run Ant files, so I simply run as “Ant Build”.
]]>public RecognizerTask(File sphinxDirectory,
String dictionary,
String lm) {
pocketsphinx.setLogfile(sphinxDirectory.getAbsolutePath() + "/pocketsphinx.log");
Config c = new Config();
c.setString("-hmm", sphinxDirectory + "/hmm/en_US/hub4wsj_sc_8k");
c.setString("-dict", sphinxDirectory + "/lm/en_US/" + dictionary);
c.setString("-lm", sphinxDirectory + "/lm/en_US/" + lm);
c.setString("-rawlogdir", sphinxDirectory.getAbsolutePath());
...
Only the constructor changed. This should allow for us to use other dictionaries and lm files fairly easily without having to muck with RecognizerTask.
The next step was to eliminate the unnecessary functionality of PocketSphinxDemo. I removed the performance calculations and edited the code. The author did something funny with making “that” references—which were final references tothis. I’m not sure the reasoning and would welcome explanation from the erudite among you. I changed the IDs of the widgets to more meaningful ones. I also triggered the asset-to-storage copy. This class was the result:
package edu.cmu.pocketsphinx.demo;
import java.io.File;
import org.twodee.android.utils.Log;
import org.twodee.android.utils.Utilities;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.EditText;
public class PocketSphinxDemo extends Activity implements RecognitionListener {
static {
System.loadLibrary("pocketsphinx_jni");
}
/** Recognizer task, which runs in a worker thread. */
RecognizerTask recognizer;
/** Are we listening? */
boolean isListening;
/** Text box for results. */
EditText editText;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Copy out assets directory to storage. TODO: condition this on files
// missing.
Utilities.copyAsset(this, "pocketsphinx");
// Set up widgets.
editText = (EditText) findViewById(R.id.resultsText);
Button b = (Button) findViewById(R.id.listenButton);
b.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v,
MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
isListening = true;
recognizer.start();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
isListening = false;
recognizer.stop();
}
return false;
}
});
isListening = false;
recognizer = new RecognizerTask(new File(getExternalFilesDir(null), "pocketsphinx"), "digger_dict.dic", "digger_lm.lm");
recognizer.setRecognitionListener(this);
new Thread(recognizer).start();
}
public void onPartialResults(Bundle b) {
final String hyp = b.getString("hyp");
editText.post(new Runnable() {
public void run() {
editText.setText(hyp);
}
});
}
public void onResults(Bundle b) {
final String hyp = b.getString("hyp");
editText.post(new Runnable() {
public void run() {
editText.setText(hyp);
}
});
}
@Override
public void onError(int err) {
Log.d("Error: %d", err);
}
}
It works, once. Subsequent listens fail.
]]>Our solution is to mirror out the assets directory to storage the first time an app is run (or whenever that directory disappears). These two methods recursively copy the asset structure we want to mirror:
/**
* Copy the asset at the specified path to this app's data directory. If the
* asset is a directory, its contents are also copied.
*
* @param path
* Path to asset, relative to app's assets directory.
*/
private void copyAsset(String path) {
AssetManager manager = getAssets();
// If we have a directory, we make it and recurse. If a file, we copy its
// contents.
try {
String[] contents = manager.list(path);
// The documentation suggests that list throws an IOException, but doesn't
// say under what conditions. It'd be nice if it did so when the path was
// to a file. That doesn't appear to be the case. If the returned array is
// null or has 0 length, we assume the path is to a file. This means empty
// directories will get turned into files.
if (contents == null || contents.length == 0)
throw new IOException();
// Make the directory.
File dir = new File(getExternalFilesDir(null), path);
dir.mkdirs();
// Recurse on the contents.
for (String entry : contents) {
copyAsset(path + "/" + entry);
}
} catch (IOException e) {
copyFileAsset(path);
}
}
/**
* Copy the asset file specified by path to app's data directory. Assumes
* parent directories have already been created.
*
* @param path
* Path to asset, relative to app's assets directory.
*/
private void copyFileAsset(String path) {
File file = new File(getExternalFilesDir(null), path);
try {
InputStream in = getAssets().open(path);
OutputStream out = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
out.close();
in.close();
} catch (IOException e) {
Log.e(e);
}
}
]]>