Java Metronome | Visual Display Sync Problem

Hi all,

I’m currently working on a metronome app and thanks to this awesome community (special thanks to philfrei!) the audio section is working really well. Now I’m working on syncing a visual display to the audio and it’s not working quite right. For testing purposes the visual is two JLabels that display the current beat and change color.

I’m basically trying to get these two visual displays to update at exactly the same time, but they don’t always change at the same time, especially at faster tempos. This is essentially the problem I was previously having with the audio because I was trying to play multiple files simultaneously via different threads. Philfrei’s fix for the audio was timing it to the audio hardware which worked perfectly, but I don’t believe I can use that same method with the visual and I have to rely on Java’s timing to update the visual when it is supposed to.

My goal is to eventually move this over to mobile devices and I’d prefer not to use 3rd party libraries so that I know how it functions and I can move it over to mobile when the time comes.

I created a short example that I could post to the forum which illustrates the problem I’m having. Here’s a brief explanation of each of my java files:

TimingCheck.java creates and displays the GUI and it also generates and plays the metronome audio tone. The audio part is different from my actual metronome program, but for the purpose of posting this is a lot shorter.

The visual part is essentially the same as my metronome program, but for simplicity of making this example I did just “copy and paste” the same code over again where I placed the comments “VISUAL 1” and “VISUAL 2” instead of looping through arrays.

VisualCalc.java is a loop that calculates when the visual should change and adds the time in milliseconds to an ArrayList in Visual.java. The value that is added to the ArrayList in Visual.java is offset by the value of “visualBuffer”, in order to match the latency created by adding the audio tone and playing it. Without this delay the visual would display way before the audio tone plays.

Visual.java is a loop that checks the system time to see if the visual needs to be changed and it updates the JLabel in TimingCheck.java.

Before posting my sample code, I wanted to mention that I’ve also been doing some research into game engines and I was wondering if something like that would be better suited for my metronome application? My current approach kind of feels like the audio and visual are totally separate elements each doing their own thing on their own time, but I want these two things to be tightly connected so they don’t drift apart over time. I’m not totally sure if trying to develop a game engine would work better or if I’d be better off trying to get my current approach to work.

I’d greatly appreciate any help in getting the visual display synced up to the audio. Thanks! :slight_smile:

Here’s my sample code:

TimingCheck.java

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;
	static Visual visual;
	static VisualCalc visualCalc;
	
	// CHANGE TEMPO
	int tempo = 60;
	
	JFrame mainFrame;
	JPanel mainContent;
	JPanel center;
	JButton buttonPlay;
	JLabel[] beatDisplay;
	
	double frequencyOfTick = 800;
	int sampleRate = 44100;
	int bufferSize = 2000;
	
	byte[] buffer;
	int tickBufferSize;
	double[] tickBuffer;
	SourceDataLine line = null;
	int tickLength = sampleRate / 10;
	
	boolean isPlaying = false;
	long systemStartTime;
	
	public static void main (String[] args) {		
		timingCheck = new TimingCheck();
		
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				timingCheck.guiCreateAndShow();
			}
		});
	}
	
	public TimingCheck(){
		tickBufferSize = (int)((60 / (double)tempo) * sampleRate);
		visual = new Visual(this);
		visualCalc = new VisualCalc(this, visual);
		generateTick();
	}
	
	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));
		
		beatDisplay = new JLabel[2];
		
		beatDisplay[0] = new JLabel("~ SOUND 1 ~");
		beatDisplay[0].setPreferredSize(new Dimension(400, 50));
		beatDisplay[0].setHorizontalAlignment(SwingConstants.CENTER);
		beatDisplay[0].setOpaque(true);
		beatDisplay[0].setFont(new Font("Serif", Font.BOLD, 40));
		
		beatDisplay[1] = new JLabel("~ SOUND 2 ~");
		beatDisplay[1].setPreferredSize(new Dimension(400, 50));
		beatDisplay[1].setHorizontalAlignment(SwingConstants.CENTER);
		beatDisplay[1].setOpaque(true);
		beatDisplay[1].setFont(new Font("Serif", Font.BOLD, 40));
		
		center.add(buttonPlay);
		center.add(beatDisplay[0]);
		center.add(beatDisplay[1]);
		mainContent.add(center, BorderLayout.CENTER);
	}
	
	public void actionPerformed(ActionEvent e) {
		if (!isPlaying) {
			isPlaying = true;
			createPlayer();
			
			systemStartTime = System.currentTimeMillis();
			
			new Thread(visual).start();
			new Thread(visualCalc).start();
			new Thread(play).start();
		}
		else {
			isPlaying = 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 (isPlaying) {
			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();
	}
}

VisualCalc.java

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

public class VisualCalc implements Runnable{
	private static TimingCheck timingCheck;
	private static Visual visual;
	
	int					visualBuffer = 3100;	
	long					msQuarterNoteDelay;
	long					currentTime;
	boolean				isPlayingTEMP=false;
	
	// VISUAL 1
	long					visual1_msAdd=0;
	int					visual1_subdivision=1;
	
	// VISUAL 2
	long					visual2_msAdd=0;
	int					visual2_subdivision=1;
	
	public VisualCalc(TimingCheck timingCheckIn, Visual visualIn){
		timingCheck = timingCheckIn;
		visual = visualIn;
		msQuarterNoteDelay = (long)(60000 / timingCheck.tempo);
	}
	
	public void run(){
		
		visual.visual1_msUpdateArray.add((long)visualBuffer);
		visual.visual2_msUpdateArray.add((long)visualBuffer);
		
		while (true){
			currentTime = getCurrentProgramTime();
			if (timingCheck.isPlaying){
				
				// VISUAL 1	
				if (currentTime >= visual1_msAdd){
					addNextVisual1();					
				}
				
				// VISUAL 2
				if (currentTime >= visual2_msAdd){
					addNextVisual2();
				}
			}
		}
	}
	
	public long getCurrentProgramTime(){
		return System.currentTimeMillis() - timingCheck.systemStartTime;
	}
	
	// VISUAL 1
	public void addNextVisual1(){
		visual1_msAdd += (long)(msQuarterNoteDelay / visual1_subdivision);
		visual.visual1_msUpdateArray.add(visual1_msAdd + visualBuffer);
	}
	
	// VISUAL 2
	public void addNextVisual2(){
		visual2_msAdd += (long)(msQuarterNoteDelay / visual2_subdivision);
		visual.visual2_msUpdateArray.add(visual2_msAdd + visualBuffer);
	}
}

Visual.java

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

import java.util.ArrayList;
import java.util.Vector;

public class Visual implements Runnable{
	private static TimingCheck timingCheck;
	
	long					currentTime;
	
	// VISUAL 1
	ArrayList<Long>	visual1_msUpdateArray = new ArrayList<Long>();
	int					visual1_subdivision=1;
	int					visual1_maxBeatCount = 4;
	int					visual1_beatCount = 1;
	boolean				visual1_green = true;
	
	// VISUAL 2
	ArrayList<Long>	visual2_msUpdateArray = new ArrayList<Long>();
	int					visual2_subdivision=1;
	int					visual2_maxBeatCount = 4;
	int					visual2_beatCount = 1;
	boolean				visual2_green = true;
	
	public Visual(TimingCheck timingCheckIn){
		timingCheck = timingCheckIn;
	}
	
	public void run(){
		while (true){
			currentTime = getCurrentProgramTime();
			
			if (timingCheck.isPlaying){
				
				// DOES NOT WORK WITHOUT THIS SLIGHT DELAY		
				try { Thread.sleep(1); } catch (InterruptedException ie){}
				
				// VISUAL 1			
				if (!visual1_msUpdateArray.isEmpty()){				
					if (currentTime >= visual1_msUpdateArray.get(0)){
						updateVisual1();
						visual1_msUpdateArray.remove(0);						
					}					
				}
				
				// VISUAL 2
				if (!visual2_msUpdateArray.isEmpty()){
					if (currentTime >= visual2_msUpdateArray.get(0)){
						updateVisual2();
						visual2_msUpdateArray.remove(0);
					}
				}
			}
		}
	}
	
	public long getCurrentProgramTime(){
		return System.currentTimeMillis() - timingCheck.systemStartTime;
	}
	
	// VISUAL 1
	public void updateVisual1(){
		timingCheck.beatDisplay[0].setText("" + visual1_beatCount++);

 		if (visual1_green){
 			timingCheck.beatDisplay[0].setBackground(Color.GREEN);
 			visual1_green = false;
 		}
 		else{
 			timingCheck.beatDisplay[0].setBackground(Color.YELLOW);
 			visual1_green = true;
 		}
				
		if (visual1_beatCount > visual1_maxBeatCount){
			visual1_beatCount = 1;
		}
	}
	
	// VISUAL 2
	public void updateVisual2(){
		timingCheck.beatDisplay[1].setText("" + visual2_beatCount++);
		
		if (visual2_green){
 			timingCheck.beatDisplay[1].setBackground(Color.GREEN);
 			visual2_green = false;
 		}
 		else{
 			timingCheck.beatDisplay[1].setBackground(Color.YELLOW);
 			visual2_green = true;
 		}
				
		if (visual2_beatCount > visual2_maxBeatCount){
			visual2_beatCount = 1;
		}
	}
}

Gulp. That’s a lot of code to read.

Are you familiar with Java “listeners”? I think the concept is also known as the “Observer” design pattern.

Try this:

(1) On the audio thread, on the very sample that starts a tone, also flag the label for display. Do nothing more than toggle a boolean (the boolean should be volatile), as we don’t want the audio processing thread to block with extraneous activities.

(2) I am assuming that you have a ‘game loop’ of some sort, for showing visual frames at some fixed rate (e.g., 60 fps is a popular choice for games). In your game loop, consult the boolean and use that to decide whether to display or not.

In effect, the item being displayed is an observer or listener to the audio event, and is “registered” in the audio thread.

Since the visual frames occur at a much slower rate than the audio samples (60 fps versus 44100 fps), an additional layer of lag time will be built in (besides the end stage graphics and audio renderings). Perhaps that will suffice?

I’m curious what results you get, if you try this method. I use it in an app that only has gradual sounds, and it looks fine. There are variably glowing lights that are “listening” to the evolving audio volume envelopes of individually associated synth notes, and the synth is set to have a slowish attack and long decay. Your usage will be a better test of the method.

Yeah, it’s a little lengthy, I posted the whole thing in case anyone wanted to run it and/or tinker with it. This was about as short as I could get it. The important stuff is really the visual creation and update in the two “Visual” files.

I am familiar with listeners…but how does the flag indicate to play at a specific time when setting it in the audio loop? Because the audio loop that loads the buffer and counts frames doesn’t update in steady consistent time, it flies through the loop as it’s filling the audio buffer and then waits while the buffer plays. Or would I essentially update the visual when the buffer plays? If I did it that way, then making a smaller audio buffer would also allow for a higher visual frame rate, correct?

I don’t actually have a game loop going yet. I was researching into it to see if that would be a good approach, but haven’t made anything like that yet. I currently have a few “simultaneous loops” going, so it’s not all consolidated under a single “game loop”.

Currently, I don’t update the visual at a specific frame rate. It loop through and checks the system time to see when the visual needs to change, when it needs to change then it just goes and immediately updates the JLabels. Should I be updating these JLabel at a specific frame rate instead? I’m not doing any animations or anything at this point, but are there still benefits of using a frame rate even in my simple example just using JLabels? If so, then maybe this is part of my problem?

Thanks again!

[quote]Currently, I don’t update the visual at a specific frame rate. It loop through and checks the system time to see when the visual needs to change, when it needs to change then it just goes and immediately updates the JLabels. Should I be updating these JLabel at a specific frame rate instead? I’m not doing any animations or anything at this point, but are there still benefits of using a frame rate even in my simple example just using JLabels? If so, then maybe this is part of my problem?
[/quote]
Most screen monitors are set to around 60Hz or maybe a little faster, and most game animation is done at 60 fps as well. There’s a diminishing returns aspect of trying to update visuals faster than that, especially when the update out paces the screen refresh. Game animation at 60 fps is considered to be quite decent and should be sufficient for your metronome, it seems to me. It might be possible to get even more precise coordination with the sound but that would be moot since displays update very infrequently in comparison to audio frame rates.

It could very well work to write code to update visually “on demand”. I’ve never tried it on anything with continuous animation. I strongly suspect getting a proper game loop structure going will be sufficient and simpler. Have you written one before?

The simplest game loop coding would be to use a util.Timer. You can put any needed updates and renders in a “TimerTask” and set the TimerTask to loop every 15 millis. The rendering code can branch on the boolean that handles the state of the JLabel, using that to decide what to show. (Most game loops use while loops instead, but if you don’t have one to pull as a template from other code you’ve written, I doubt the trouble to write a first one of that form, from scratch, is justified for the scant amount of visuals or state updating you are doing.)

Handing off information between threads via variables is an important technique. It helps keep objects from getting too enmeshed and codependent, and helps with multi-threading. I think the proper term is “loosely coupled”, for this sort of communication. If it works, great! If not, I am thinking of trying a strategy where the audio thread “time stamps” those requests to draw, but uses the audio frame number as the stamp value, not the actual processing moment of the audio. But hopefully you won’t have to go there.

Are you familiar with the term “volatile”? Make the state boolean for the JLabel volatile, to help ensure timely and non-blocking communication between the audio and game-loop/visual threads.

I’ve made a game engine following along with a tutorial video on youtube. The concept of the game engine itself doesn’t seem all that difficult, but it would require me to start over for the most part on my program since I did it very differently. I wanted to get some thoughts on whether or not a game engine would be worthwhile before rewriting everything…and it seems like it might be worth it.

My main question about the while loop method is using Thread.sleep() to set the frame rate…that’s what the tutorial I followed used. From researching good metronome design I saw numerous people stating this is horrible for accurate timing, at least for timing audio…but is it ok to Thread.sleep() use for setting the frame rate of the program or is that going to cause other timing issues also? Would it be better to enter a “do nothing” loop while constantly checking system time to see when it needs to break out of the loop?

At the moment I’m not doing a lot of state updating, but I will be eventually…this is only the beginning. :slight_smile: I have big plans for this metronome in terms of making it the best metronome app for mobile devices. I play multiple instruments, but drums are my main one so I intend to add some drum-related features that are outside the scope of a typical metronome (like polyrhythms for example). However, my focus right now is getting a rock solid foundation to build on before I even think about piling on features. A metronome with tons of features is total junk if it doesn’t keep excellent time. In that regard, this application has been very different from the previous MIDI applications I’ve made because the Java MIDI sequencer handled all the audio timing stuff so all I had to worry about was the program logic and the music theory stuff. Timing the audio has been a much bigger pain in the neck than I thought it was going to be when I first started on this project as I didn’t realize how many ways Java messed up the timing. lol

Ok, I see what you’re saying about time stamping the visual, that makes more sense now.

I wasn’t familiar with the term “volatile” as it related to Java variables. But I’ve done the research on it now and that certainly sounds like a necessary tool to have when working with threads.

Thanks again philfrei!

There’s a lot of Java to learn, to do what you wish to do. You seem to me to be making good progress.

When you say “game engine”, I think you mean to “game loop”. An “engine” implies a whole slew of features that will enable a user to build a game via various templates and tools, not just the visual display loop.

It is my guess that if you have a visual display fps that is fine-grained enough, and supporting code that updates and reports the metronome state as cleanly as possible, that this will suffice. 60 fps is considered a good target for games. (Film is often 24 fps, or they do something like use 72 fps but show each frame 3 times. People don’t usually notice synching discrepancies in the theaters, do they?)

Part of the reason I think a lack of precision (relative to that needed to make audio work) for sound/visual coordination is that the brain is biased towards fuzzing together sound and visuals as unified events. I’m not sure how to account for this. Maybe it has to do with the fact that the synching happens “higher up” in the nervous system, at some point where visual and aural systems meet, rather than at the more “mechanical” point of contact in the eye or ear. Or maybe it has something to do with sound travelling slower in air than light, thus lagging by varying amounts depending upon distance, i.e., inherently unreliable beyond a certain degree of precision anyway.

Looping the visual display at fixed time amounts by the standard technique of calculating the varying amounts of compensating Sleep will probably work okay. I think you just have to try it and see. I prefer using a util.Timer, as it is just as accurate (I cite “Killer Game Programming” as evidence) and somewhat simpler to code. There is a scheduling command that actively tries to keep the timer locked on a fixed schedule, making adjustments as best as it can if scheduling tasks run either slow or fast. To me that beats calculating sleep intervals, both in terms of the coding involved, and in the hope that the implementers know what they are doing and are handling this task at a level closer to native code. I could be wrong though and the util.Timer scheduling management could be implemented via higher level code similar to what we would write.

You’re not doing a lot of “motion” animation are you? More like there are things blinking on and off, right?

The idea of “time stamping” with the audio frame count will only contribute to the extent that the visual frame loop is super solid. If it is, then you could also count visual frames and work out a calculation as to what audio frame range should be reported within a given visual frame. (Main issue would be figuring out a good “0” reference frame.)

But I don’t think visual frames are displayed with as much precision as for playing back sound. You are getting into new territory for me. I’ve been able to be helpful because I’m a step or two ahead in a few regards, but that is all. There is much about the graphics system that I do not know.

Not just Java, remember I’m also going to be moving over to mobile devices, which I’m sure will open up numerous cans of worms! Yes, I am a glutton for punishment, in case you were wondering! ;D

I’m pretty sure the tutorial I went through was actually a game engine. It did have a file called “game loop” but it had other stuff too, like key and mouse handling, updating, drawing, etc. in a later video series he made a pacman game using this engine…I only watched a tiny bit of the pacman stuff though.

[quote]Looping the visual display at fixed time amounts by the standard technique of calculating the varying amounts of compensating Sleep will probably work okay.
[/quote]
I’m not sure I understand what you mean by the “standard technique of calculating the varying amounts of compensating Sleep”…do you mean calculating the delay by checking the system time and then sleeping for that amount of time, or something else?

[quote]I prefer using a util.Timer, as it is just as accurate (I cite “Killer Game Programming” as evidence) and somewhat simpler to code.
[/quote]
I saw this post which states using the util timer isn’t recommended for “heavy lifting” and that timer tasks should complete quickly…

I see you also posted in that thread and brought up the “scheduleAtFixedRate(…)” method, so would this be ok to use for heavy processing tasks? Is the normal “schedule(…)” method the only one that would cause issues with the util timer when doing heavy processing?

I will be adding a lot more state checking, calculating, and actions to my program as I start adding on features and whatever method I decide to go with now needs to be able to accommodate future additions. I also don’t mind going a more difficult route and putting in more work if that means it will work better overall and allow me to do more things with it.

As far as animations, I might eventually include some kind of a “back and forth” LED type display or a pendulum style metronome display, which would likely be done with sprite sheets updated at a constant rate (depending on tempo of course). That’s probably the most animation I would need for a metronome app.

Thanks again!

It’s a way of getting a “fixed time” loop. At the start of the loop, obtain the current nanotime. Do the loop’s tasks: the update and render, then check the time again. If the “fixed time” amount has not elapsed yet, calculate and sleep the amount of time required to fill out the interval. Even fancier if you want to code it: if you have previously gone over in terms of processing time for a given frame, a fixed time loop algorithm can be written to “play catch up” by going a bit shorter (if there is time left over this iteration) than the required fixed time amount, until the frame loop is back at the correct time slot.

To a large extent, the timing issues that are discussed on that past thread that you quoted are no longer so important. AFAIK, the new Microsoft OS’s use a faster system clock now. At that time, it only updated every 15 or 16 milliseconds. In other words, if you aren’t expecting to market to Windows XP users, not to worry.

IMHO, the scheduleAtFixedRate type scheduling is perfectly adequate. Regardless of method, if the update and render takes longer than the scheduled time, you are going to get slippage in the frame rate. I don’t see how the distinction “heavy lifting” actually means anything useful in this context. Either the tasks can be done within the designated frame rate or they can’t. The wrapper for that code should have little or no influence on the execution speed of the tasks being wrapped.

A little slippage in frame rate may not even matter, as long as you are cleanly updating and reporting the metronome state at the time that the frame executes.

I continue to suspect that you might get more accurate scheduling with the scheduleAtFixedRate use of the util.Timer, versus calculating the variable sleep interval yourself, depending on how the Java engineers have implemented this internally. (I assume they know what they are doing.)

Maybe someone with a different opinion will speak up. Usually we hear something if I make an inaccurate remark. But given my tendency to tl;dr posts, perhaps my controversial statements are just be being overlooked. :slight_smile:

If you want to, you can also look at the ScheduledThreadPoolExecutor as an upgrade to the util.Timer. This gives you some additional exception handling capabilities (in case the TimeTask throws an unchecked exception). Learning about ExecutorServices and what Brian Goetz (“Java Concurrency in Practice”) describes as “exploitable parallelism” is probably overkill. It would certainly be an interesting and very involved side trip! I’m in no position to evaluate the pros and cons.

I’ve been reading your posts, luckily I like reading long posts.

In all honesty, the human body corrects for visual errors to a certain extent. The most important thing is to make sure that the frame rate is consistent and your sound never skips a beat. In other words, if anything goes wrong just make the visuals skip into the right location. All that matters is that your music doesn’t suffer during frame drops or other unforeseen disaster of computer hardware. This is no less trivial than any problem games have with syncing music to visuals.

[quote] It’s a way of getting a “fixed time” loop. At the start of the loop, obtain the current nanotime. Do the loop’s tasks: the update and render, then check the time again. If the “fixed time” amount has not elapsed yet, calculate and sleep the amount of time required to fill out the interval.
[/quote]
Ok, that’s essentially what I thought…I just wanted to make sure that’s what you meant.

[quote] Regardless of method, if the update and render takes longer than the scheduled time, you are going to get slippage in the frame rate. I don’t see how the distinction “heavy lifting” actually means anything useful in this context.
[/quote]
That could very well be what he was referring to. Here’s what it says in the javadoc, so you are probably right in that this is just referring to problems of going over the allowed time you give it.

  • “Timer tasks should complete quickly. If a timer task takes excessive time to complete, it “hogs” the timer’s task execution thread. This can, in turn, delay the execution of subsequent tasks, which may “bunch up” and execute in rapid succession when (and if) the offending task finally completes.”
    http://docs.oracle.com/javase/6/docs/api/java/util/Timer.html

Seems like they could have worded that a little better then…instead of saying “complete quickly” which is really vague and doesn’t mention it being related to the delay that you set.

I will give this a go and keep you posted. Thanks again for all your help philfrei.

Hi ctomni231, thanks for the reply.

The audio tests I’ve done for about 10 minutes have been very solid so far. And once the visual part is finished, I plan on doing some much longer “over night” tests to see if everything stays right on the money for a really long period of time. In my actual program (where the audio part is done very differently from the example I posted here) I do have a delay in place on the audio side so that the audio has more than enough time to load the sounds and play smoothly. If any audio problems occur, hopefully they will be fixable by increasing the delay. I’ll know that for sure after some longer tests.

Thanks again!