Java Audio Metronome | Timing and Speed Problems

Hi Riven, thanks for the reply!

I wasn’t actually assuming the visual latency would be “instant”…but at the moment my focus is on the audio end of the program such as determining how I’m going to be mixing and playing my audio as well as trying to determine the precise moment that the audio will be playing and how far behind my program is operating when it says “play sound” and it actually plays that sound. I will eventually be doing the same thing for the visual display, but I haven’t gotten to the point figuring out my visual method yet. Right now I’m just updating a JLabel with some text and color, but I’m sure that will change once I start actually working on the GUI.

Also, it seems like an audio buffer with even 1000 samples isn’t nothing…1/44100*1000 = 22.6ms. So in regards to my previous question if it ends up blocking when the second buffer tries to add data, then my program would respond almost two buffers behind which could be around 40+ms. Which may actually put it in nearly perfect sync if the visual latency is about the same as this…I totally meant to do that! ;D

It’s great to know what kind of range I should try to hit with the audio and visual synchronization. My ultimate goal is to develop this app for different mobile devices and I’m sure the range of latencies of visual display and audio playback will vary so it’ll likely be impossible to get all of them “perfect”.

Thanks again!

philfrei, I’ve tried running your Theremin applet within a couple IDE’s (jGrasp and Netbeans)…jGrasp wouldn’t even open it at all and while Netbeans did open it, the option to run it was grayed out. So still no luck on the Theremin. :frowning:

Thanks for elucidating this, Riven.

Tekkerue, as to the question about blocking: if the BlockingQueue is asked to do something that it can do, then the code executes. If it can’t do it, then it blocks. So, I pick your scenario (b). But the lack of real time guarantees in Java makes this moot if you want metronomic accuracy.

The article I cited earlier goes into this: “Real Time Low Latency Audio Processing”. It’s going to be vital that you understand the nature of Java’s lack of real time guarantees if you are going to use javax.sound.sampled for this.

For accuracy on par with Java’s MIDI sequencer, the only way I know of is to calculate the actual sound frame a note should play, count frames, and start the note on that frame (when the while loop is processing that frame). The moment in time at which sound data is loaded into the buffer is too variable relative to when it is actually heard to do otherwise.

Example: 44100 fps stereo, 16-bit (“CD Quality”). Let’s say you want the metronome to play 120 bpm. I would calculate the interval in frames ( = 22050 ) and have the while loop count frames as it processes the buffers of PCM bytes, and start the metronome click sound on that actual frame. So, in the middle of some buffer when you get to frame number N * 22050, you start the loading of the PCM data for the click sound.

I’m guessing you may have to hear for yourself before taking the trouble to use the frame counting method. I first ran into the ‘lack of real time guarantees’ issue with the Theremin, trying to get the sound to accurately track the mouse movements via a MouseMotionListener. My rude awakening (and considerable resistance to the truth) can be viewed on an old JGO thread. Eventually I got a reasonable solution.

Java’s MIDI package and sequencer can provide the needed accuracy–so maybe you should just use that, and schedule Midi ‘control messages’ to trigger your visuals.

Doesn’t NetBeans or your other IDE have a way to run Applets? I use Eclipse.
Doh! :clue: I forgot to give you the entry point:
Here’s the HTML code that is used for that jar:


<script src="http://www.java.com/js/deployJava.js"></script>
<script> 
    var attributes = {code:'jTheremin.ThereminApplet.class',
                      archive:'JThereminApplet.jar',
                      width:810, height:400} ;
    var parameters = {fontSize:10} ;
    var version = '1.6' ;
 
    deployJava.runApplet(attributes, parameters, version);
</script>

You know, there is the program “appletviewer.exe” that is in the bin file of your JDK, probably.
http://docs.oracle.com/javase/7/docs/technotes/tools/windows/appletviewer.html


I haven’t tested this, but I think if you put the above html in a valid html file, and put it in the same directory as the jar, you can run it via a command line.

Hi philfrei,

I read the article you provided, so I do understand what Java does to muck with the timing…but I also know that people have somehow been able to make it work. :slight_smile: If I recall correctly, Y-Metronome was made with Java and I use this one quite regularly. I also have a few different metronome apps on my Android phone that work great, and as you know Android is done with Java. So it is possible to get great results using Java.

In terms of “counting frames”, isn’t that essentially what I’m doing by determining the time in nanoseconds that the sound should start playing? I’m specifically calculating the point that the audio should play…I guess I’m not totally sure what you mean by “frame”…?

I did look into the MIDI route, but unfortunately it doesn’t appear that MIDI sequencers are supported on mobile OS’s. Not even on Android. It appears there is very basic MIDI support in that you can play a MIDI file, but doing the kind of low level on the fly MIDI synthesis is not supported for mobile. :frowning:

I do have an appletviewer, so I will look into that soon…hopefully tomorrow.

BTW, have you had any luck getting my Random Chord Progression Generator to load? I can’t load that either via the web browser.

…D’oh, never mind! I see what you mean by “frame” as you have 44100fps. I’m too used to calling them SAMPLES instead of frames. lol

Ok, so if I understand what you’re saying…

You keep track of every frame that plays by incrementing a frame counter by the size of the buffer every time you write to the line.
Then instead of doing math on an ever incrementing number of “nanoseconds” I would simply do that for an ever incrementing number of frames.

And since the GUI part wouldn’t be running on “frames” per say…I could convert the frames to nanoseconds with 1 frame = 22675ns at 44100fps.

Am I on the right track here?..

[quote]Am I on the right track here?..
[/quote]
Yes,

[quote]BTW, have you had any luck getting my Random Chord Progression Generator to load? I can’t load that either via the web browser.
[/quote]
I haven’t tried yet. Something about the way I’m wired, I will burn all sorts of time writing answers to questions and then get even further behind in everything else I am doing. :stuck_out_tongue: Next window of opportunity will be Sunday. Will be looking forward to it!

Woohoo! Looks like I have my plan forward…now off to write some code! Thanks again for your help and I’ll stop taking up all your time so you can get caught up on your own stuff now. :wink:

IT’S WORKING!!!


http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

Just wanted to let you know that you are a genius philfrei! :slight_smile: Using the method you described everything is working fantastically now. I even tried it at 2000bpm and it works!..no that’s not a typo! ;D It does glitch if I try to do other things while it’s running like minimize/maximize windows, etc but if I just leave the computer alone it plays perfectly at 2000bpm. Not that anyone would ever have a need for 2000bpm, but that is still way awesome it can do that! lol I could probably make it stop glitching completely if I increase the time between when it tries to load the sound and when it actually plays. I had to adjust it so that it played perfectly even at slower tempos as when I first got it playing it was taking to long to load the sounds so they weren’t playing when they were supposed to.

And just to let you know how long I have been trying to create this metronome application…I recently came across an old thread of mine over on JavaRanch from 4 years ago when I was trying to make this metronome app using MIDI instead of audio…but I never got anywhere with it. Now it’s working using audio and it should be able to work on mobile devices as well (in an ideal world of course! lol)…

I’ve had the idea to create the ultimate “one metronome to rule them all” for a while, but it seems like I’ve run into road blocks at every turn just trying to get well timed and consistent clicks that didn’t glitch or drop out or whatever…and thanks to you, it works great now! Seriously, you have no idea how ecstatic I am right now!!

Thank you again for all your help!!

More happy dance…


http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

http://www.sherv.net/cm/emo/funny/2/banana.gif

Hello again!

So, I just tried running the metronome on a desktop, recording it on a laptop, then checking the timing using my recording software…and I found that my metronome is speeding up ever so gradually. I also tried going back to a much simpler and straight forward metronome version I made from before (see code below) and tried recording that, but it too speeds up gradually by nearly the exact same amount (they were only a few frames apart after 10 minutes).

After exactly 10 minutes the simple version was 323 frames ahead and my current version was 314 frames ahead, these are approximately 7ms ahead of where the click should be. This is strange because I figured that if the timing was going to be off at all it would be slightly behind the beat if the process of loading the buffer added a little bit more time…but instead it is ahead of the beat and I can’t figure that one out.

Unless I’m missing something, this metronome should be sample accurate and should not be drifting at all. I used 60bpm so a tone should be added exactly every 44100 samples thus there shouldn’t be any cascading rounding errors or anything. I’m not seeing anything that I’m doing wrong…so could this be a hardware problem or a JVM problem?

I would really like to be able to record for an hour (or longer) and have it match the tempo grid PERFECTLY in my recording software the entire time. Is there anything more I can do to guarantee perfect timing or is this as good as it can get using Java?

Thanks!

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import java.io.*;
import javax.sound.sampled.*;

public class TimingCheck implements ActionListener {
	static TimingCheck timingCheck;

	JFrame mainFrame;
	JPanel mainContent;
	JPanel center;
	JButton buttonPlay;
	
	int bpm = 60;
	double frequencyOfTick = 800;
	int sampleRate = 44100;
	int bufferSize = 2000;
	
	byte[] buffer;
	int tickBufferSize;
	double[] tickBuffer;
	SourceDataLine line = null;	
	int tickLength = 4100;
	
	boolean playing = false;
	
	public TimingCheck(){
		tickBufferSize = (int)((60 / (double)bpm) * (double)sampleRate);
	}
	
	public static void main (String[] args) {		
		timingCheck = new TimingCheck();
		
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				timingCheck.guiCreateAndShow();
			}
		});
	}
	
	public void guiCreateAndShow() {
		guiFrameAndContentPanel();
		guiAddContent();
	}
	public void guiFrameAndContentPanel() {
		mainContent = new JPanel();
		mainContent.setLayout(new BorderLayout());
		mainContent.setPreferredSize(new Dimension(500,500));
		mainContent.setOpaque(true);
		
		mainFrame = new JFrame("Timing Check");				
		mainFrame.setContentPane(mainContent);				
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainFrame.pack();
		mainFrame.setVisible(true);
	}
	public void guiAddContent() {
		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;
			timingCheck.createPlayer();
			timingCheck.generateTick();
			new Thread(play).start();
		}
		else {
			playing = false;
		}
	}
	
	public void generateTick(){
		tickBuffer = new double[tickBufferSize];
		for (int i = 0; i < tickLength; i++)
			tickBuffer[i] = Math.sin(2 * Math.PI * i / (sampleRate/frequencyOfTick));
		for (int i = tickLength; i < tickBufferSize; i++)
			tickBuffer[i] = 0;
	}
	
	public void createPlayer(){
		AudioFormat af = new AudioFormat(sampleRate, 16, 1, true, false);
		try {
			line = AudioSystem.getSourceDataLine(af);
			line.open(af);
			line.start();
		}
		catch (Exception ex) { ex.printStackTrace(); }
	}

	public Runnable play = new Runnable() { public void run() {
		buffer = new byte[bufferSize];
		int b=0;
		int t=0;
				
		while (playing) {
			if (t >= tickBufferSize)
				t = 0;
			
			short maxSample = (short) ((tickBuffer[t++] * Short.MAX_VALUE));
			buffer[b++] = (byte) (maxSample & 0x00ff);			
			buffer[b++] = (byte) ((maxSample & 0xff00) >>> 8);
			
			if (b >= bufferSize) {
				line.write(buffer, 0, buffer.length);
				b=0;
			}
		}
		
		destroyPlayer();
	}};
	
	public void destroyPlayer(){
		line.drain();
		line.close();
	}
}

You are talking about less than 1/100th of a second after 10 minutes, when comparing the timing mechanisms of two entirely different pieces of software.

Maybe this is normal, I don’t know. Perhaps this is why we have things like SMPTE code, and other mechanisms for maintaining synchronization?

Is tickBufferSize calculating out to what you expect? Bouncing back and forth between doubles and ints is worrisome. You only need to have the denominator of the division be a double (I don’t know if making the sample rate a double adds to the error or not). Maybe test for a simple tempo like 60bpm or 120bpm, and eliminate this calculation – hard code in 44100 or 22050 as your tickBufferSize and see if the error still occurs.

It occurs to me the (int) cast at the end could be causing a slight truncation in the tickBufferSize, which would create a slightly faster than expected rendition.

How many seconds in 10 minutes? 600? How many tickBuffers in 10 minutes? (Depends on tempo.) Are you testing a tempo (e.g., slower than 60bpm, or some “awkward” number) that would create the shown error if the tickBufferSize was off by one due to the int cast?

Another thought: take a piece of music, a wav that you have in your DAW, and play it back with Java, and record that in the DAW and compare. Or compare with other software that can play back.

…just a quick addition.

For comparison I just tried recording one of the current metronome apps I have on my phone (Samsung Galaxy S4) called Mobile Metronome, which the top free metronome for Android (with over 1 million downloads) and after 10 minutes of recording it was 3,229 samples (or approximately 73ms) behind the beat. So mine is actually over 10x more accurate than the top freebie, not to shabby. :slight_smile: So maybe this is just something I should be expecting and just have to live with? Although, my mind is going all kinds of places like trying to figure out how to now easily calculate the drift and then create a drift “offset” that would nudge back it enough to keep it on beat…OCD and programming, not a good mix. lol

Hi philfrei,

I realize it’s a small delay and different software…but I’ve moved recorded music tracks around between different software and computers before with no shift in the timing. A friend and I used to bounce tracks back and forth between his Mac laptop and my PC desktop (he used Logic and I use Magix Samplitude) and there wasn’t any timing drift problems as long as my project’s tempo matched his project’s tempo (or vice versa). But maybe any difference between them would have been too small to notice with instrument tracks?..it’s very easy to tell exactly where the start of my metronome tone begins since everything surrounding it is all zero’s, but that wouldn’t be the case with instrument tracks.

I just added in a print statement for tickBufferSize and it does come back out to equal exactly 44100. The tempo I tested was 60bpm, which I did to avoid any weird cascading rounding errors as you mentioned just to see if it would stay “spot on”.

I love your idea of trying to play a sound file in Java and recording that. I’ll record 10 minutes of the metronome within my recording software Samplitude so that its easy to compare the difference between the Java playback version and the original. I’ll need to figure out how to load the wave file into my program, because I’d like to use the same SourceDataLine method I’m using for my metronome.

Thanks again for all your help! :slight_smile:

Alrighty, I got the WAV file reader working. BTW, I found some great source code on Stack Overflow from a google search that I was able to use to get it going…it was posted by an extremely helpful guy! :wink:


…what a small world! ;D

I recorded 10 min of Samplitude’s metronome at 60bpm as a mono wav file, played it through my Java application, re-recorded it via the laptop, and it too is drifting ahead of time by 268 samples or approximately 6ms after 10 minutes. So this method had a little less drift than my metronome.

EDIT: I should mention that I did check the initial recording of Samplitude’s metronome (just to be sure) by loading it back into Samplitude and it was perfect down to the sample at 10 min. No drift at all. This is the file I played via Java.

Here is the code:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

import java.io.*;
import javax.sound.sampled.*;

public class WavReader implements ActionListener {
	static WavReader wavReader;
	
	JFrame mainFrame;
	JPanel mainContent;
	JPanel center;
	JButton buttonPlay;
	
	int bufferSize = 2000;
	byte[] buffer;
	SourceDataLine line = null;
	
	boolean playing = false;
	
	File fileIn;	
	AudioInputStream audioInputStream;
	
	public WavReader(){
		fileIn = new File("SamplitudeMetronomeMono.wav");
	}
	
	public static void main (String[] args) {		
		wavReader = new WavReader();
		
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				wavReader.guiCreateAndShow();
			}
		});
	}
	
	public void guiCreateAndShow() {
		guiFrameAndContentPanel();
		guiAddContent();
	}
	public void guiFrameAndContentPanel() {
		mainContent = new JPanel();
		mainContent.setLayout(new BorderLayout());
		mainContent.setPreferredSize(new Dimension(500,500));
		mainContent.setOpaque(true);
		
		mainFrame = new JFrame("Timing Check");				
		mainFrame.setContentPane(mainContent);				
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainFrame.pack();
		mainFrame.setVisible(true);
	}
	public void guiAddContent() {
		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;
			loadWaveFile();
			createPlayer();
			new Thread(play).start();
		}
		else {
			playing = false;
		}
	}
	
	public void loadWaveFile(){
		try {
			audioInputStream = AudioSystem.getAudioInputStream(fileIn);
		} catch (Exception e) { e.printStackTrace(); }
	}
	
	public void createPlayer(){
		AudioFormat af = audioInputStream.getFormat();
		try {
			line = AudioSystem.getSourceDataLine(af);
			line.open(af);
			line.start();
		}
		catch (Exception e) { e.printStackTrace(); }
	}
	
	public Runnable play = new Runnable() { public void run() {
		buffer = new byte[bufferSize];
		int b=0;
		
		try { 
			while (playing && (audioInputStream.read(buffer) != -1)) {
				line.write(buffer, 0, buffer.length);
			}
		} catch (Exception e){ e.printStackTrace(); }
		
		destroyPlayer();
	}};
	
	public void destroyPlayer(){
		line.drain();
		line.close();
	}
}

Interesting!

I think it is very likely that if you go back to some of those tracks you swapped, there might indeed be some drift. Ears have to be really good, and circumstances very clear to hear imprecisions when only a few millis are involved. I recall once trying to improvise against a friend’s track and having the feeling be slightly uncomfortable, unable to get a sense of being “in the pocket”. We took a closer look at the rhythms and found one of the drums offset by 4 millis. Straightening that out did a lot to settle my nerves. But I didn’t hear it as a mistake, but rather just experienced some tension that I couldn’t really explain. It could also have been an example of me just being nervous, and the fix working as a sort of placebo. But my experience was that the fix did feel more solid. (And yes, non-percussive sounds would be next to impossible to detect as being off at this level.)

I tested the idea of the int cast truncation, as well, and found that the “correct” number of frames was being generated, to the nearest frame. Later on, it occurred to me: what if a proper DAW is even more accurate than the sample rate? In order to handle a plethora of sample rates, maybe the DAWs work at a finer granularity than 44100 fps, and the metronomes naturally are tied to the highest level of precision available in that DAW. (My Cakewalk Sonar also handles 48000 sample rate, so it has to have something that is more precise than 44100 fps, but aren’t there 96000 tracks now, as well?)

If a DAW’s metronome is more accurate than the 44100 sample rate, then the fraction of the frame that is getting truncated in Java, but is handled in the DAW’s metronome could cause some drift that is in the ballpark for what you are getting. Maybe?

I’m not sure how the fractions would be handled in the DAW. Maybe something like a Leap Year, adding or taking away an extra frame every now and then? As far as Java is concerned, there is no way I know of to take nanosecond readings at the precise time the frame is run through the digital-to-analog-converter, and the lack of real-time guarantees means the point when the frame is processed in the while loop could vary considerably!

In any event, your demonstration that the metronome is as or more accurate than other commercial metronomes seems like a good reality check. I wonder how your metronome, when running in Android, will compare.

Last bit of musing: if the DAW’s metronome IS more accurate (to fractions of a sample), then a recording made from a DAW would most likely be a tiny bit longer than the calculated number of frames (using the truncation that Java does with the int cast). Perhaps that led to the drift in the plus, instead of minus direction, for that test?

I forgot about that post you dug up. I was contradicting the answer of one of my Java heros, Kay Horstmann! I was also doing a bit of venting. Maybe if you like that answer, and found it helpful, you’ll give it a +?

Sometimes I wish I had minored in mathematics and/or sound engineering. I have a friend that is an engineer and used to work for Pro Tools. I’ll run the idea of the metronome vs sample rate accuracy issue by him next chance I get.

Surely there must be a way to query/bruteforce the maximum supported samplerate of the hardware.

It would have been interesting to have done some really fine comparisons between our programs. Can’t really do so now because my friend no longer lives in the area. Also, in Samplitude when you zoom all the way in on a waveform it actually chops it up using vertical lines to show the value of every individual sample…I’m not sure if Logic does that.

Being more accurate than 44100 was my initial thinking as well, which is why my thinking was to use nanoseconds for timing…but then Java wouldn’t allow that to work. :-\ However, that seems like it would have helped more with the odd tempos like 61bpm or similar since there would have to be “rounding” involved. But with 60bpm, that is one beat every second so it seems like each beat should theoretically be in the same place regardless of the sample rate…44100 or 96000 frames is divided over the span of a second, but it seems like that very first frame should always be in the same place. It’s strange for sure! I’m very interested to hear what your friend who worked for Pro Tools has to say. Was he a programmer who worked on coding Pro Tools?

I too wonder how well this will work when I move over to Android…hopefully the difference is in the coding, not the platform. I’m gonna be bummed if it ends up getting worse when moved to mobile.

Maybe the best solution is just to provide an “offset” value in the settings? Just in case the accuracy does change when moved to mobile, then for those who want it “dead on accurate” they could calibrate it for their specific device and punch in the offset value. Now it would be way cool if I could figure out how to do that automatically! Such as automatically record 1 min of click internally, calculate the drift, and then adjust the offset all at the press of a button! Man that would be slick! ;D

I tried to give your post on StackOverflow a “+”, but it said I have to have 15 reputation points…I’m only at 11! Pffffft!

I minored in physics…which is essentially math + additional headaches. LOL

Riven,
I’m not sure that the sample rate is the problem for my tests at 60bpm. Even if the sample rate was ridiculously low at like 100Hz, the beat should still start on the very first frame at the start of each second. Higher sample rates would certainly help for beats that start in between, but it seems like that first frame should always start at the start of the second on the dot.

Internal clock issues maybe? Hmm…brain storm! I think tomorrow I’ll try recording Samplitude’s click straight out of Samplitude (no Java) and record it via the laptop in Reaper. Two different DAW software on two different computers. If this comes out to be slightly off time as well, then I think we can conclude that this little bit of timing drift is to be expected.

I’m fairly sure philfrei meant that you might be sending 44100 samples per second to hardware that outputs 48000 samples per second.

This means that every sample is ‘scaled in time’ by factor 1.0884353741496598639455782312925 (48000 /44100)

Some transformation has to happen in hardware, and it might not be optimal, leading to a drift of a few millis over a timespan of 10 minutes. A drift of 7ms over 10601000ms means it is 99.9988333% accurate, which seems reasonable for consumer grade audio hardware.

That’s why I suggest to use the native sampling rate of your audio hardware, as to get rid of this truncation/inaccuracy altogether (hopefully).

ISTR that since Windows Vista the entire Windows audio pipeline is now implemented purely in software. I may be wrong.

Cas :slight_smile:

Ok, I have some new test results! I also re-recorded 44.1kHz just to see if it was the same as before, and it actually had slightly less drift this time…

TimingCheck.java:
44.1kHz = 295 samples (approx. 6ms) ahead of the beat
48kHz = 1,232 samples (approx. 27ms) behind the beat
96kHz = 297 samples (approx. 6ms) ahead of the beat

NO Java - metronome track played in Samplitude:
44.1kHz (both devices) = 289 samples (approx. 6ms) ahead of the beat

For some reason 48kHz was off by quite a bit more than the others and it was the only one to drift behind the beat. So that’s interesting.

I think you were right on the money philfrei, the drift is simply because it’s two different applications running on two different devices. Even two different DAW’s on different computers were not in sync perfectly after 10 minutes and were off by approximately the same amount as the Java program.

Guess I should stop worrying about this now…eh? ;D