Playing an arbitrary tone with Android
I originally found this example code on a blog, but it had some bugs in it that generated some horrendous sounds. I've fixed the bugs and posted the resulting code here. Seems to work well for me!
public class PlaySound extends Activity { // originally from http://marblemice.blogspot.com/2010/04/generate-and-play-tone-in-android.html // and modified by Steve Pomeroy <steve@staticfree.info> private final int duration = 3; // seconds private final int sampleRate = 8000; private final int numSamples = duration * sampleRate; private final double sample[] = new double[numSamples]; private final double freqOfTone = 440; // hz private final byte generatedSnd[] = new byte[2 * numSamples]; Handler handler = new Handler(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onResume() { super.onResume(); // Use a new tread as this can take a while final Thread thread = new Thread(new Runnable() { public void run() { genTone(); handler.post(new Runnable() { public void run() { playSound(); } }); } }); thread.start(); } void genTone(){ // fill out the array for (int i = 0; i < numSamples; ++i) { sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone)); } // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. int idx = 0; for (final double dVal : sample) { // scale to maximum amplitude final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } } void playSound(){ final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length, AudioTrack.MODE_STATIC); audioTrack.write(generatedSnd, 0, generatedSnd.length); audioTrack.play(); }}
Improving on the above code:
Add amplitude ramp up and ramp down to avoid the clicks.
Add code to determine when the tack has finished playing.
double duration = 1; // secondsdouble freqOfTone = 1000; // hzint sampleRate = 8000; // a numberdouble dnumSamples = duration * sampleRate;dnumSamples = Math.ceil(dnumSamples);int numSamples = (int) dnumSamples;double sample[] = new double[numSamples];byte generatedSnd[] = new byte[2 * numSamples];for (int i = 0; i < numSamples; ++i) { // Fill the sample array sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate));}// convert to 16 bit pcm sound array// assumes the sample buffer is normalized.// convert to 16 bit pcm sound array// assumes the sample buffer is normalised.int idx = 0;int i = 0 ;int ramp = numSamples / 20 ; // Amplitude ramp as a percent of sample countfor (i = 0; i< ramp; ++i) { // Ramp amplitude up (to avoid clicks) double dVal = sample[i]; // Ramp up to maximum final short val = (short) ((dVal * 32767 * i/ramp)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);}for (i = i; i< numSamples - ramp; ++i) { // Max amplitude for most of the samples double dVal = sample[i]; // scale to maximum amplitude final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);}for (i = i; i< numSamples; ++i) { // Ramp amplitude down double dVal = sample[i]; // Ramp down to zero final short val = (short) ((dVal * 32767 * (numSamples-i)/ramp )); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);}AudioTrack audioTrack = null; // Get audio tracktry { audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, (int)numSamples*2, AudioTrack.MODE_STATIC); audioTrack.write(generatedSnd, 0, generatedSnd.length); // Load the track audioTrack.play(); // Play the track}catch (Exception e){ RunTimeError("Error: " + e); return false;}int x =0;do{ // Monitor playback to find when done if (audioTrack != null) x = audioTrack.getPlaybackHeadPosition(); else x = numSamples;} while (x<numSamples);if (audioTrack != null) audioTrack.release(); // Track play done. Release track.
I wrapped the above wonderful solutions into a neat little package that's more useable out of the box as a simple configurable buzzer. It runs it in a background thread and has stop and play methods and a handful of options you can set.
It's up on JCenter so you can add it to your dependencies list like this
compile 'net.mabboud:android-tone-player:0.2'
and you use it like this for a continuous buzzer
ContinuousBuzzer tonePlayer = new ContinuousBuzzer();tonePlayer.play();// just an example don't actually use Thread.sleep in your appThread.sleep(1000); tonePlayer.stop();
or a buzzer played only once and you can set frequency and volume like this
OneTimeBuzzer buzzer = new OneTimeBuzzer();buzzer.setDuration(5);// volume values are from 0-100buzzer.setVolume(50);buzzer.setToneFreqInHz(110);