I am working on an App in which the user can touch the screen and depending on where he/she touches a certain tone is played. To achieve this I have the following code:
@Override
public boolean onTouchEvent(MotionEvent event) {
// Calculate the frequency that should be played
final double frequency = (event.getX()/mScreenWidth)*450 + 450;
// Use a new tread as this can take a while
mThread = new Thread(new Runnable() {
public void run() {
generateTone(frequency);
mHandler.post(new Runnable() {
public void run() {
playSound();
}
});
}
});
mThread.start();
return true;
}
void generateTone(double frequency) {
// Fill out the array
for (int i = 0; i < NUM_SAMPLES; ++i) SAMPLE_ARRAY[i] = Math.sin(2 * Math.PI * i / (SAMPLE_RATE/frequency));
// Convert to 16 bit PCM sound array
// Assumes the sample buffer is normalized.
int idx = 0;
for (final double dVal : SAMPLE_ARRAY) {
// Scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// In 16 bit WAV PCM, first byte is the low order byte
GENERATE_SND[idx++] = (byte) (val & 0x00ff);
GENERATE_SND[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
void playSound(){
final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, GENERATE_SND.length, AudioTrack.MODE_STATIC);
audioTrack.write(GENERATE_SND, 0, GENERATE_SND.length);
try {
audioTrack.play();
} catch(IllegalStateException exception) {
Log.d("My App", "Tried to play sound, but audio track is not ready yet");
}
}
This kind of works, but when the user moves his/her finger over the screen more rapidly I get the following error:
12-28 11:19:14.928: E/AndroidRuntime(10433): FATAL EXCEPTION: main
12-28 11:19:14.928: E/AndroidRuntime(10433): java.lang.IllegalStateException: play() called on uninitialized AudioTrack.
12-28 11:19:14.928: E/AndroidRuntime(10433): at android.media.AudioTrack.play(AudioTrack.java:899)
12-28 11:19:14.928: E/AndroidRuntime(10433): at com.mynamespace.projectone.Main.playSound(Main.java:77)
12-28 11:19:14.928: E/AndroidRuntime(10433): at com.mynamespace.projectone.Main$1$1.run(Main.java:47)
12-28 11:19:14.928: E/AndroidRuntime(10433): at android.os.Handler.handleCallback(Handler.java:615)
12-28 11:19:14.928: E/AndroidRuntime(10433): at android.os.Handler.dispatchMessage(Handler.java:92)
12-28 11:19:14.928: E/AndroidRuntime(10433): at android.os.Looper.loop(Looper.java:137)
12-28 11:19:14.928: E/AndroidRuntime(10433): at android.app.ActivityThread.main(ActivityThread.java:4931)
12-28 11:19:14.928: E/AndroidRuntime(10433): at java.lang.reflect.Method.invokeNative(Native Method)
12-28 11:19:14.928: E/AndroidRuntime(10433): at java.lang.reflect.Method.invoke(Method.java:511)
12-28 11:19:14.928: E/AndroidRuntime(10433): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
12-28 11:19:14.928: E/AndroidRuntime(10433): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
12-28 11:19:14.928: E/AndroidRuntime(10433): at dalvik.system.NativeStart.main(Native Method)
At the moment I catch this error with the try/catch construction around audioTrack.play()
so the app doesn't crash. This is not a solution though, because the sound still stops playing and doesn't restart. Any ideas how to fix this problem?
If the mode is static, then you need to call write() before calling play().
If the mode is streaming, then it doesn't matter. You can optionally prime the data using play() before calling write(), or you can write() and then play(). Either way, it's legal.