Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play a sound every N milliseconds

I'm developing a metronome application. The user can select at runtime the bpm, and my app will play the "tick" sound accordingly. The "tick" is a single metronome "shot" (mp3). I tried implementing it using Handler and MediaPlayer, but the metronome is not precise at all. So I thought about changing the whole approach: when the user choose a new bpm value, I synthesize a new sound by repeating the tick sound X times every N milliseconds, then looping over this runtime created sound. Is this a valid alternative? How can it be implemented in Android?

like image 633
Raffaele Avatar asked May 16 '11 14:05

Raffaele


2 Answers

The alternative of looping through a synthesized sound seems to be the best choice for now. There was a great session about audio on Google I/O 2013 called High Performance Audio that I would certainly advice watching for having a deeper understanding on how the system works and what problems the developers will face when dealing with the audio latency. At about the 17:00 of the video, there is graph that shows jitter versus callbacks. In the perfect world that does not exist (oh really?), the jitter would be zero for all the scheduled audio callbacks made. But that is not the case, for there are jitters as high as 35 milliseconds or even greater, for the data in the graph was made using an unspecified ICS device and there are certainly worse scenarios than that.

So, as a metronome is a precision tool and these jitters are not good at all, the scheduled playback approach should be left aside. I even made an reasonably realiable metronome work with a synthesized sound using AudioTrack.

Hope it helps ^^

like image 93
Alesqui Avatar answered Oct 22 '22 15:10

Alesqui


You could try to use a TimerTask scheduled for fixed-rate execution on a Timer.

Timer and TimerTask are both part of the Android SDK (and Java SE). The executions do not delay because of execution time of the previous event.

Timer timer = new Timer("MetronomeTimer", true);
TimerTask tone = new TimerTask(){
     @Override
     public void run(){
         //Play sound
     }
};
timer.scheduleAtFixedRate(tone, 500, 500); //120 BPM. Executes every 500 ms.

You can then cancel the TimerTask when you need to change the BPM.

tone.cancel();
tone = new TimerTask(){...}
timer.scheduleAtFixedRate(tone, 1000, 1000); //60 BPM. Executes every 1000 ms.

Another possibility that may meet your requirements (from your comments) is spinning a thread and checking System.nanoTime() and sleeping in increments but spinning when you get close to wake up.

long delayNanos = 500000000;
long wakeup = System.nanoTime() + delayNanos; //Half second from right now
long now;

while(!done){
     now = System.nanoTime();

     //If we are less than 50 milliseconds from wake up. Spin away. 
     if(now <= wakeup - 50000000){
          //Sleep in very small increments, so we don't spin unrestricted.

          Thread.sleep(10); 
     }
     if(now >= wakeup){
           //Play sound
           wakeup += delayNanos;
     }
}
like image 44
Dev Avatar answered Oct 22 '22 17:10

Dev