multithreading - Java Audio Metronome | Timing and Speed Problems -


i’m starting work on music/metronome application in java , i’m running problems timing , speed.

for testing purposes i’m trying play 2 sine wave tones @ same time @ regular intervals, instead play in sync few beats , out of sync few beats , in sync again few beats.

from researching metronome programming, found thread.sleep() horrible timing, avoided , went checking system.nanotime() determine when sounds should play.

i’m using audiosystem’s sourcedataline audio player , i’m using thread each tone polls system.nanotime() in order determine when sound should play. create new sourcedataline , delete previous 1 each time sound plays, because volume fluctuates if leave line open , keep playing sounds on same line. create player before polling nanotime() player created , has play sound when time.

in theory seemed method getting each sound play on time, it’s not working correctly. i’m not sure if timing problems running different threads or if has deleting , recreating sourcedataline or if it’s in playing sounds or exactly...

at moment simple test in java, goal create app on mobile devices (android, ios, windows phone, etc)...however current method isn’t keeping perfect time on pc, i’m worried mobile devices limited resources have more timing problems. adding more sounds create more complex rhythms, needs able handle multiple sounds going simultaneously without sounds lagging.

another problem i’m having max tempo controlled length of tone since tones don’t overlap each other. tried adding additional threads every tone played own thread...but screwed timing, took out. have way overlap previous sound allow higher tempos.

any getting these timing , speed issues straightened out appreciated! thanks.

soundtest.java:

import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*;  import java.io.*; import javax.sound.sampled.*;  public class soundtest implements actionlistener { static soundtest soundtest;   // enable/disable sounds boolean playsound1  = true; boolean playsound2  = true;   jframe mainframe; jpanel maincontent; jpanel center; jbutton buttonplay;  int samplerate = 44100; long starttime;  sourcedataline line = null;  int ticklength; boolean playing = false;  soundelement sound01; soundelement sound02;  public static void main (string[] args) {            soundtest = new soundtest();      swingutilities.invokelater(new runnable() { public void run() {         soundtest.gui_createandshow();     }}); }  public void gui_createandshow() {     gui_frameandcontentpanel();     gui_addcontent(); }  public void gui_frameandcontentpanel() {     maincontent = new jpanel();     maincontent.setlayout(new borderlayout());     maincontent.setpreferredsize(new dimension(500,500));     maincontent.setopaque(true);      mainframe = new jframe("sound test");                    mainframe.setcontentpane(maincontent);                   mainframe.setdefaultcloseoperation(jframe.exit_on_close);     mainframe.pack();     mainframe.setvisible(true); }  public void gui_addcontent() {     jpanel center = new jpanel();     center.setopaque(true);      buttonplay = new jbutton("play / stop");     buttonplay.setactioncommand("play");     buttonplay.addactionlistener(this);     buttonplay.setpreferredsize(new dimension(200, 50));      center.add(buttonplay);     maincontent.add(center, borderlayout.center); }  public void actionperformed(actionevent e) {     if (!playing) {         playing = true;          if (playsound1)             sound01 = new soundelement(this, 800, 1);         if (playsound2)             sound02 = new soundelement(this, 1200, 1);          starttime = system.nanotime();          if (playsound1)             new thread(sound01).start();         if (playsound2)             new thread(sound02).start();     }     else {         playing = false;     } } } 

soundelement.java

import java.io.*; import javax.sound.sampled.*;  public class soundelement implements runnable { soundtest soundtest;   // tempo change // 750000000=80bpm | 300000000=200bpm | 200000000=300bpm long nsdelay = 750000000;   int clicklength = 4100;  byte[] audiofile; double clickfrequency; double subdivision; sourcedataline line = null; long audiofileplay;  public soundelement(soundtest soundtestin, double clickfrequencyin, double subdivisionin){     soundtest = soundtestin;     clickfrequency = clickfrequencyin;     subdivision = subdivisionin;     generateaudiofile(); }  public void generateaudiofile(){     audiofile = new byte[clicklength * 2];     double temp;     short maxsample;      int p=0;     (int = 0; < audiofile.length;){         temp = math.sin(2 * math.pi * p++ / (soundtest.samplerate/clickfrequency));         maxsample = (short) (temp * short.max_value);         audiofile[i++] = (byte) (maxsample & 0x00ff);                    audiofile[i++] = (byte) ((maxsample & 0xff00) >>> 8);     } }  public void run() {     createplayer();     audiofileplay = soundtest.starttime + nsdelay;      while (soundtest.playing){         if (system.nanotime() >= audiofileplay){             play();             destroyplayer();             createplayer();             audiofileplay += nsdelay;         }     }     try { destroyplayer(); } catch (exception e) { } }  public void createplayer(){     audioformat af = new audioformat(soundtest.samplerate, 16, 1, true, false);     try {         line = audiosystem.getsourcedataline(af);         line.open(af);         line.start();     }     catch (exception ex) { ex.printstacktrace(); } }  public void play(){     line.write(audiofile, 0, audiofile.length); }  public void destroyplayer(){     line.drain();     line.close(); } } 

this sort of thing difficult right. have realise in order play sound, has loaded audio driver (and possibly sound card). takes time, , have account that. there 2 options you:

  1. rather counting down delay between every beat, count down delay start, when metronome activates. example, instance want beat every second. because of ~20ms delay, in old method you'd beats @ 20ms, 1040, 2060, 3080, etc... if count down start , place beats @ 1000, 2000, 3000, etc. play @ 20ms, 1020, 2020, 3020, etc... there still variance since dalay varies bit, there should 1000ms between beats , not go out of sync (or @ least, problem not worse on time , can't heard).

  2. the better option, , 1 of such programs use, generate larger pieces of music. buffer instance 20 seconds ahead , play that. timing should perfect during 20 seconds. when 20 seconds on must generate new sound. if can find out how this, should append new waveform old , have play continuously. otherwise, generate new 20 second soundbit , accept delay between them.

now problem sounds not being able overlap... i'm no expert , don't know answer, know: has mix sounds if need them overlap. either can in software combining waveform bytes (i think it's addition in logarithmic space), or need send different overlapping sounds different 'channels', in case audio driver or sound card you. don't know how works in java though, or forgot, learned through trial-and-error , working .mod files.


Comments

Popular posts from this blog

database - VFP Grid + SQL server 2008 - grid not showing correctly -

jquery - Set jPicker field to empty value -

.htaccess - htaccess convert request to clean url and add slash at the end of the url -