Update: Thanks to xxv for spotting a bug in my encoding. He posted the updated code on stackoverflow, here is the link and made a comment here to let me know. Basically I was getting the byte order wrong in the encoding and forgetting to factor in the sampleRate into the wav- oops and sorry! I have update the code here.
Original post.
I threw this together the other night just to figure out how to do some very simple sound synthesis. I am not going to go into details about how the code works as it is pretty obvious for the most part.
It basically generate a sine wave at a given frequency then converts this into a 16bit pcm format and finally plays it. I used a thread to make sure my generation does not block.
Sound synthesis is something I would like to spend some more time messing with. I have written some Haskell code that lets me play with some synthesis ideas, nothing elaborate. One day I will get round to porting it to Java and making some synthesis apps for android.
Here is the code.
public class GenerateSound extends Activity {
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 = 100; // 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
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 (double dVal : sample) {
short val = (short) (dVal * 32767);
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
void playSound(){
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
8000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, numSamples,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, numSamples);
audioTrack.play();
}
}
I tried out your code and fixed a few bugs. The modified code is posted on StackOverflow: http://stackoverflow.com/questions/2413426/playing-an-arbitrary-tone-with-android
ReplyDeleteThanks for the code! Let me know if there are any issues with posting the code there.
Hi xxv,
ReplyDeleteThanks for looking at the code. I had recently used the wav encoding parts in my GWT experiments and spent some time wondering why they sounded terrible. Eventually I tracked it down to the order of the bytes but it had not occurred to me that I had published this code otherwise I would have updated this entry.
So thanks for taking the time to figure out and fix the bug, it had me pulling my hair out for a couple of evenings while I blamed my newer code.
I will update the code in my post.
Paul
There were a couple other items, too. For example, why do you have the "+ 0.5" there? And the sine generation is incorrect, as you need to account to the sample rate.
ReplyDeleteHi xxv,
ReplyDeleteThe +0.5 is to round towards zero the nearest short rather than round down towards zero, but then again I can never remember what happens with negative numbers in this instance so probably best removed.
Oops on the sampleRate! Updating code yet again! This will teach me to publish code without doing the proper testing.
Thanks again and updating code yet again
Hey I'm having problems with adding two tones together. I thought it would be just averaging two sine waves together but that doesn't work for some reason.
ReplyDeleteHey there,
ReplyDeleteWhen adding sine waves together you need to renormalise them so they don't go outside the range of +/-1. So if you add together two tones divide by 2. You can think of +1 as representing the max volume of the speaker.
You can also have fun with multipling tones together. Take a look at http://obiwannabe.co.uk/tutorials/html/tutorials_main.html for lots of ideas.
What is to be done with the main file??????????
ReplyDeleteHi..Can u help me with something..I was able to successfully implement this much..in this the tone is played for a particular duration which is set in code..I want to modify it such that the tone continues to play till the stop button is pressed..and not for a preset duration..I am unable to implement it..can you please help with this?
ReplyDeleteI think you can use the Audio track for this. It has some functionality to let the set the number of times (or forever) to play using setLoopPoints().
DeleteAlthough googling indicates you may have a short delay before it restarts each time. If that is the case then using SoundPool or MediaPlayer may be a better option. Although I have no experience with them so don't how easy it will be to get a synthesized sound into them and play it.
Sorry don't have my android development handy at the moment to try it out.