Playing an arbitrary tone with Android
Asked Answered
K

10

102

Is there any way to make Android emit a sound of arbitrary frequency (meaning, I don't want to have pre-recorded sound files)?

I've looked around and ToneGenerator was the only thing I was able to find that was even close, but it seems to only be capable of outputting the standard DTMF tones.

Any ideas?

Klaipeda answered 9/3, 2010 at 23:30 Comment(3)
Did you find any real solution?Nehru
No, but I ended up not doing the project.Klaipeda
@JeremyLogan And you got positive negative feedback. lol.Ascocarp
S
117

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 <[email protected]>
    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();
    }
}
Significative answered 9/3, 2010 at 23:31 Comment(24)
Is this line correct? audioTrack.write(generatedSnd, 0, numSamples); or should it be numSamples * 2 because there are 2 bytes per sample. Also the write method also takes an array of shorts so what is the advantage of creating an intermediary array of bytes?Broadwater
This is indeed a great example, thanks a lot. However I found another nasty bug (if you extend the code), which is: audioTrack.write(generatedSnd, 0, numSamples) should be audioTrack.write(generatedSnd, 0, 2*numSamples) or better audioTrack.write(generatedSnd, 0, generatedSnd.length);Arrowworm
The code sample has been updated based on the above two comments. Thanks!Significative
This code doesn't work quite right in my experience; when the CPU is busy the tone plays for a duration that's much shorter than the one specified in the code.Karilla
Are you sure this code is good? For me it does not work well. Checked with audacity and the sounds are totally different. So I set the samplerate to 44100 and it gives similar values as audicity in certain cases, but for 9-10-11 KHz it emits the same sound and above 14 KHz it does not emit anything. I had to manually set the multiplyer for each KHz to make it work (originally 2 in the Math.sin function), but I still cannot emit a sound above 21.863 KHz.Absorptance
I haven't tried it in a while, so it's possible that something's wrong. It's open for updates if anyone is feeling like twiddling with it.Significative
@Absorptance Fmax = samplerate / 2, so it seems correct. Fmax is approx 22khz.Frigidarium
Instead of using "numSamples" in the AudioTrack constructor, you should use generatedSnd.length because The fifth parameter is "buffer size in bytes." The example only plays the first half of the tone.Tad
@Tad nice catch. Corrected.Significative
Hi..Good code..I want to modify it such that the tone continues to play till stop button is pressed instead of the duration being set earlier..can u help with how to do that?Boneblack
For doing that you should call playSound(); every time you want to play the sound, and when you want to stop it you should do this code: audioTrack.flush(); audioTrack.stop(); audioTrack.release(); It works for me!Oubliette
Then I've got a question too: can someone explain the meaning of the following: 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); Help is much appreciatedOubliette
@Black27 The samples are created in floating points with an amplitude range from 0.0 to 1.0. Multiply by 32767 would convert it into the 16-bit fixed point range. The AudioTrack expects the buffer to be little endian format. Hence the next two line just converts the byte order from big endian into little endian.Fornof
@ephramd in order to loop the sound, after you write to the audio track, but before you play do: audioTrack.setLoopPoints(0, generatedSnd.length / 2, -1);Neusatz
@ephramd @Neusatz I found that dividing by 4 worked for me: audioTrack.setLoopPoints(0, generatedSnd.length / 4, -1);Clavicle
Hi, tone-up is very low only 250mV though the volume is max. other app can put out 2.xV. I want to put out 2.xV also, can you help me?Goldarn
Hi, how about STEREO ?Goldarn
can we save this voice? i mean which is played by audio track?Alterative
@Absorptance are you facing if you set the frequency to a hight number, let's say 22000hz, there will be lots of noises come together?Centri
I have problem with this code, as the sound plays but randomly stops halfway. It may play all tones, only first 10%, 30%, 70% or so on. Anyone has similar problem?Shirring
using private static final int sampleRate = 192000; I was able to play ultra-sonicLowell
Why is the intermediary "sample" array needed? Is it an optimization of sorts?Nellienellir
And also why does there need to be another handler with its own run() inside the thread's run()?Nellienellir
Don't get me wrong, this works, but this is a messy code. Remember, if you need a comment, you count that a defeat.Eucken
E
32

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;            // seconds
double freqOfTone = 1000;       // hz
int sampleRate = 8000;          // a number

double 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 count


for (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 track
try {
    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.
Euell answered 26/11, 2012 at 12:44 Comment(6)
The primary change was the ramp up and down of the amplitude. The original code started and ended with maximum amplitude. This produce clicks at the start and end of the tone. This code ramps the amplitude up from 0 to full amplitude over the first 20% of the samples. It then ramps down from full amplitude to zero over the last 20% of the samples. The tones are smoother and much more pleasant. The other change was to monitor the playing of the tone and not continue until the tone finished playing.Euell
I cudnt get it to run..I am able to run the first one..but cant really understand how to modify it to what u have done..it would be really helpful as I am looking to get rid of the click sound..Boneblack
+1, but the code in this answer doesn't come close to compiling. I've implemented it correctly here: gist.github.com/SuspendedPhan/7596139 Just replace Steve's genTone() method with mine and you'll get the ramping effect.Synagogue
Since there is a memory leak on MODE_STATIC, I modified the code to use MODE_STREAM belowVespucci
Starting with API, it's possible to do the ramp using setVolume(). This enables just looping a very small sample and even playing a sound for an dynamic length (e.g. while the user holds a buttion). Code example: github.com/stefanhaustein/android-tone-generator/blob/master/…Eskill
I used this code, it ramps the begining of the sound but at the end, I still hears clicks ... can anyone help me with this?Auxesis
S
9

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 app
Thread.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-100
buzzer.setVolume(50);
buzzer.setToneFreqInHz(110);

Extended blog post here about it here GitHub here

Sachasachem answered 1/10, 2016 at 3:18 Comment(1)
@Melchester it's fixed now. Thanks for the heads up and sorry about thatSachasachem
V
5

Since there is a bug in some older android versions that causes a memory leak when using MODE_STATIC, I modified Xarph's answer above to use MODE_STREAM. Hopefully it will help some.

public void playTone(double freqOfTone, double duration) {
 //double duration = 1000;                // seconds
 //   double freqOfTone = 1000;           // hz
    int sampleRate = 8000;              // a number

    double 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 count


    for (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 track
    try {
         int bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, bufferSize,
                AudioTrack.MODE_STREAM);
        audioTrack.play();                                          // Play the track
        audioTrack.write(generatedSnd, 0, generatedSnd.length);     // Load the track
    }
    catch (Exception e){
    }
    if (audioTrack != null) audioTrack.release();           // Track play done. Release track.
}
Vespucci answered 30/4, 2014 at 22:19 Comment(0)
B
3

Here's another blog demoing a simple synth plus some UI

http://audioprograming.wordpress.com/2012/10/18/a-simple-synth-in-android-step-by-step-guide-using-the-java-sdk/

You might also be interested in csound or pdlib (pure data lib) for android.

Burl answered 6/7, 2013 at 18:16 Comment(0)
C
3

Modified Code Based on Singhaks' answer

public class MainActivity extends Activity {
    private final int duration = 30; // 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();
    private AudioTrack audioTrack;
    private boolean play = false;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                8000, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, numSamples,
                AudioTrack.MODE_STREAM);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Use a new tread as this can take a while
        Thread thread = new Thread(new Runnable() {
            public void run() {

                handler.post(new Runnable() {

                    public void run() {
                        playSound();
                        genTone();
                    }
                });
            }   
        });
        thread.start();
    }

    void genTone(){
        // fill out the array
        while(play){
                for (int i = 0; i < numSamples; ++i) {
                //  float angular_frequency = 
                    sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone));
                }
                int idx = 0;

                // convert to 16 bit pcm sound array
                // assumes the sample buffer is normalised.
                for (double dVal : sample) {
                    short val = (short) (dVal * 32767);
                    generatedSnd[idx++] = (byte) (val & 0x00ff);
                    generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
                }
                audioTrack.write(generatedSnd, 0, numSamples);
            }
        }


    void playSound(){
        play = true;
        audioTrack.play();
    }
}
Cyanotype answered 21/5, 2014 at 2:47 Comment(0)
A
3

Do major (16 notes)

 public class MainActivity extends AppCompatActivity {

  private double mInterval = 0.125;
  private int mSampleRate = 8000;
  private byte[] generatedSnd;

  private final double mStandardFreq = 440;

  Handler handler = new Handler();
  private AudioTrack audioTrack;


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_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() {

            byte[] tempByte = new byte[0];
            for (int i = 0; i < 16 ; i++ ){
                double note = getNoteFrequencies(i);
                byte[] tonByteNote = getTone(mInterval, mSampleRate, note);
                tempByte = concat(tonByteNote, tempByte);
            }
            generatedSnd = tempByte;

            handler.post(new Runnable() {
                public void run() {
                    playTrack(generatedSnd);
                }
            });
        }
    });
    thread.start();
  }

  public byte[] concat(byte[] a, byte[] b) {
    int aLen = a.length;
    int bLen = b.length;
    byte[] c= new byte[aLen+bLen];
    System.arraycopy(a, 0, c, 0, aLen);
    System.arraycopy(b, 0, c, aLen, bLen);
    return c;
  }

  private double getNoteFrequencies(int index){
    return mStandardFreq * Math.pow(2, (double) index/12.0d);
  }

  private byte[] getTone(double duration, int rate, double frequencies){

    int maxLength = (int)(duration * rate);
    byte generatedTone[] = new byte[2 * maxLength];

    double[] sample = new double[maxLength];
    int idx = 0;

    for (int x = 0; x < maxLength; x++){
        sample[x] = sine(x, frequencies / rate);
    }


    for (final double dVal : sample) {

        final short val = (short) ((dVal * 32767));

        // in 16 bit wav PCM, first byte is the low order byte
        generatedTone[idx++] = (byte) (val & 0x00ff);
        generatedTone[idx++] = (byte) ((val & 0xff00) >>> 8);

    }

    return generatedTone;
}

  private AudioTrack getAudioTrack(int length){

    if (audioTrack == null)
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                mSampleRate, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, length,
                AudioTrack.MODE_STATIC);

    return audioTrack;
  }

  private double sine(int x, double frequencies){
    return Math.sin(  2*Math.PI * x * frequencies);
  }

  void playTrack(byte[] generatedSnd){
    getAudioTrack(generatedSnd.length)
            .write(generatedSnd, 0, generatedSnd.length);
    audioTrack.play();
  }

}
Aliquant answered 29/10, 2016 at 11:50 Comment(0)
P
3

see this helpful library

https://github.com/karlotoy/perfectTune

it's easy to use

add this to your dependencies

 compile 'com.github.karlotoy:perfectTune:1.0.2'

And you use it like this:

PerfectTune perfectTune = new PerfectTune();
perfectTune.setTuneFreq(desire_freq);
perfectTune.playTune();

to stop the tune:

perfectTune.stopTune();
Ploy answered 23/2, 2017 at 9:46 Comment(0)
F
2

There are several programs for this, but they suck. I measured a few:

http://www.endolith.com/wordpress/2009/11/24/android-audio-applications/

So don't do whatever they do. :D

Fabe answered 28/4, 2010 at 18:12 Comment(0)
A
2
    float synth_frequency = 440;
    int minSize = AudioTrack.getMinBufferSize(SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
minSize,
AudioTrack.MODE_STREAM);
audioTrack.play();
short[] buffer = new short[minSize];
float angle = 0;
while (true) 
{
    if (play)
    {
        for (int i = 0; i < buffer.length; i++)
        {
            float angular_frequency =
            (float)(2*Math.PI) * synth_frequency / SAMPLE_RATE;
            buffer[i] = (short)(Short.MAX_VALUE * ((float) Math.sin(angle)));
            angle += angular_frequency;
    }
        audioTrack.write(buffer, 0, buffer.length);
    } 

// You can add arbitrary value in synth_frequency to get change sound for example you can add random variable to get sound

Allies answered 4/5, 2013 at 9:4 Comment(1)
You're converting it all to a short, in the end. There's no reason to do angle as a float. double math is the same speed and doesn't require a bunch of casting.Chroma

© 2022 - 2024 — McMap. All rights reserved.