Playing an arbitrary tone with Android Playing an arbitrary tone with Android android android

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);

Extended blog post here about it hereGitHub here