Synchronize sound effect with other Timer actions - java

I have this Timer that is supposed to initiate various actions at 1 second intervals. It's a pretty simple idea that simulates a 5 second countdown (literally). At the start, a JLabel is updated to set its text to "5". Simultaneously, a little mp3 sound file plays that voices the number that the user sees on the screen. Then, one second later, the text is changed to "4" and a different mp3 plays that voices the number 4. And so on until we get to zero.
This all works, but I can't get the visual updating to synchronize precisely with the audio part. The mp3 always seems to play just slightly before the screen is updated. At first, I thought that I just needed to put a little extra silence at the beginning of each mp3 on a trial and error basis until things synched up. But no matter how much silence I prepend to each mp3, I still hear the audio before the screen updates. All that changes is the lag between each "one second" update.
Anyway, here is the code that I am working with. Can anyone help me get this to synchronize? Maybe I need a second timer? I'm not sure how that would work. Thanks in advance!
class Countdown extends JFrame implements ActionListener {
private Timer countdownTimer = new Timer(1000, this);
int countdownSeconds;
MyJFrame myFrame;
public Countdown(MyJFrame thisFrame) {
int countdownSeconds = 5;
countdownTimer.start();
myFrame = thisFrame;
}
#Override
public void actionPerformed(ActionEvent e) {
if (countdownSeconds == 0) {
myFrame.updateCountdown(myFrame, "Go");
SoundEffect.play("launch.mp3");
countdownTimer.stop();
} else {
myFrame.updateCountdown(myFrame, Integer.toString(countdownSeconds));
if (countdownSeconds == 5) {SoundEffect.play("five.mp3");}
if (countdownSeconds == 4) {SoundEffect.play("four.mp3");}
if (countdownSeconds == 3) {SoundEffect.play("three.mp3");}
if (countdownSeconds == 2) {SoundEffect.play("two.mp3");}
if (countdownSeconds == 1) {SoundEffect.play("one.mp3");}
countdownSeconds--;
}
}
}
public void updateCountdown(MyJFrame thisFrame, String numSec) {
lblCountdown.setText(numSec);
}
import java.io.FileInputStream;
import javazoom.jl.player.Player;
public class SoundEffect {
public static void play(String mp3File) {
try {
FileInputStream mp3_file = new FileInputStream(mp3File);
Player mp3 = new Player(mp3_file);
mp3.play();
}
catch(Exception e) {
System.out.println(e);
}
}
}

I highly doubt you will ever be able to sync those perfectly, but I can explain why the current approach will not work.
Swing components must be updated on the Event Dispatch Thread, as you do with the Timer. When you update the text of the label, it will schedule a repaint on the Event Dispatch Thread. Note the word schedule, and not perform.
However, the Event Dispatch Thread is currently busy playing your sound, so the actual repaint operation will only occur after you called mp3.play().
Now you could (if allowed, not sure about the threading rules for playing MP3's) try to play the mp3 on a different Thread (e.g. by using a secondary non-Swing timer). But since you never have full control over when the actual repaint is going to happen and only can control when the repaint is scheduled, the visual and auditive updates can still be out-of-sync.

The major part of the problem comes down to:
if (countdownSeconds == 5) {SoundEffect.play("five.mp3");}
..leading to..
public class SoundEffect {
public static void play(String mp3File) {
try {
FileInputStream mp3_file = new FileInputStream(mp3File);
Player mp3 = new Player(mp3_file);
mp3.play();
}
catch(Exception e) {
System.out.println(e);
}
}
}
Whoa! This is not the time to be loading the clips!
Instead they should be loaded before the timer ever starts. I think the file I/O is the real cause of the (perceptible) lag or delay.

Related

Breaking a Loop With a JButton

I'm creating a program for a game that clicks in random intervals for X seconds with Y time between each click. Here is the code that does this.
try {
Util.autoCode rand = new Util.autoCode();
Robot robot = new Robot();
int NoC;
NoC = Integer.parseInt(this.numberOfClicksTF.getText().trim());
if (NoC == 0) {
while (NoC == 0) {
// robot.mousePress(InputEvent.BUTTON1_MASK);
System.out.println("Infinite Press");
Thread.sleep(rand.clickDelay());
System.out.println("Infinite Release");
// robot.mouseRelease(InputEvent.BUTTON1_MASK);
Thread.sleep(rand.interval());
break;
}
} else {
for (int i = 0; i < NoC; i++) {
//robot.mousePress(InputEvent.BUTTON1_MASK);
System.out.println("Click Press");
Thread.sleep(rand.clickDelay());
System.out.println("Click Release");
// robot.mouseRelease(InputEvent.BUTTON1_MASK);
Thread.sleep(rand.interval());
}
}
} catch (AWTException ex) {
Logger.getLogger(MainFrame.class
.getName()).log(Level.SEVERE, null, ex);
} catch (NumberFormatException ex) {
Logger.getLogger(MainFrame.class
.getName()).log(Level.SEVERE, null, ex);
} catch (InterruptedException ex) {
Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
}
I commented out the mouse press and release just so I could do a check to make sure the timing was right, and that it would perform each click. This code is started by JButton in a JFrame. Whenever I press the start button, it starts the code and nothing but force closing it in Netbeans will stop it. The goal is to have a start button initiate, and a stop button interrupt the code, but not close the JFrame. I have been looking everywhere and haven't been able to find a straight answer.
Any help is welcomed and appreciated!
You need tu use SwingWorker, for example:
in your JFrame you may have a SwingWorker and it "builder" method:
private SwingWorker worker;
private SwingWorker getWorker() {
worker = worker == null ? worker = new SwingWorker() {
#Override
protected Object doInBackground() throws Exception {
while (true) {
System.out.println("doInBackground!");
Thread.sleep(1000);
}
}
} : worker;
return worker;
}
now, in the swing main thread (used for visual components updates) you need to call the SwingWorker with your background task:
private void jButtonActionPerformed(java.awt.event.ActionEvent evt) {
if (getWorker().getState().equals(SwingWorker.StateValue.STARTED)) {
worker.cancel(true);
worker = null;
} else {
getWorker().execute();
}
}
You can click on the button as many times as you wish, the app will create and run a SwingWorker or kill and set to null the current SwingWorker.
Literally ran into your exact same problem this week trying to make almost the same thing. First, let me suggest reading up on multithreading in Java. It may seem complicated, but trust me it's necessary to understand why your program is not working. (I'll give a run down of what's happening below)
Essentially, your GUI is running on an Event Dispatcher Thread that needs to be separated from your robot. That is because when you call thread.sleep(), you essentially tell the GUI thread to sleep too, resulting in loss of GUI control. Start your robot and GUI in separate threads and have your JButtons invoke methods to get your robot thread to start and stop. For example:
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GUI();
}
});
}
To create your GUI(I don't know what your GUI is like, so I just wrote new GUI() to substitute for how you created yours), that way it's in the thread it's supposed to be in. Then in your eventListener, spawn a new thread holding the robot object everytime the start button is pressed:
if(actionEvent.getSource().equals(playButton)) { //listening for the play button
if(robotThread == null) {
autoClicker= new AutoClicker();
Thread = new Thread(autoClicker);
Thread.start();
}
}
And initiate your robot object in the autoClicker() class like you did, and have autoClicker implement runnable.
public class AutoClicker implements Runnable{
private Robot robot;
public void run(){
try {
robot = new Robot();
} catch (AWTException e) {e.printStackTrace();}
//you need to learn about synchronization first
while(true){
synchronized(this){
//do clicks and stuff here
}
}
}
}
This way, your GUI and robots are in separate threads. In order to make JButtons start and stop your robot, you're going to need to learn synchronization to know how to properly multithread.
Another thing is that you should probably use the wait() method instead of sleep(), as sleep() would cause a lot more problems that's too much to go through in a single answer. Again, you need to learn concurrency/synchronization first.
If you want comment below and we can work out a way for me to DM you my code I'm working on right now, which is the exact same thing as you're trying to make. I can explain every part of it to you to help you on your way.

Using AudioClip in Java freezes the program terribly?

I don't have any code to show in particular to link because this is quite a general question...you see, I made a small game applet with 8 AudioClip variables, and these were supposed to play every time a certain action was taken by the user. Ex: Player clicks = Shooting sound plays. The thing is, the moment I added these AudioClip files my program just freezes terribly and becomes borderline unplayable if the freezing is particularly bad.
The simple way I did it is here (From the book, actually)
AudioClip playerShooting;
playerShooting=getAudioClip(getDocumentBase(),"PlayerShooting.wav");
Then I would just use the following whenever the user clicked:
playerShooting.play():
My program worked smoothly before I added these wav files. They aren't even that long. Where did I go wrong here, and, how can I remove the random freezing?
EDIT:
This is the code I am running:
public void mouseClicked(MouseEvent e)
{
if(playerAlive)
{
timeShotsAfter=System.currentTimeMillis();
if((timeShotsAfter-timeShotsBefore)>=100)
{
if(shotIndex<10)
{
playerShooting(); //Here is where I call the function
shotFiredX[shotIndex]=currentX;
shotFiredY[shotIndex]=currentY;
shotSize[shotIndex]=20;
}
if(shotIndex<10)
shotIndex++;
else
shotIndex=0;
timeShotsBefore=System.currentTimeMillis();
}
else{}
toggleShooting=false;
}
}
This is the function:
public void playerShooting()
{
new Thread(
new Runnable() {
public void run() {
try {
playerShooting.play();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
I was having the same problem a while back. What I did to solve my problem was update to JDK 8. In previous JDK versions, audio files appear to have been an afterthought and can be buggy. If you are still having problems, JDK 8 has the ability to play mp3 files which are significantly smaller than wav (you may want to try this). Make sure you use sound.stop() when your clips are done as this might free up some memory.
Play the audio clip in another thread?
EDIT:
new Thread(
new Runnable() {
public void run() {
try {
playerShooting.play():
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
Edit:
I'm not quite sure if this part is correct:
getAudioClip(getDocumentBase(),"PlayerShooting.wav");
Try adding System.out.println(getDocumentBase()); to see whether the location is correct.

Adding a repeat function to a simple MIDI player

I am trying to implement a repeat function on a custom MIDI player, but I am unable to implement a repeat function. Here are the classes I am using:
NotePlayer - Plays MIDI notes using Java's MIDI package.
GuitarTunerGUI
Interface to the NotePlayer class.
Provides six JButtons for each guitar string, a JComboBox for selecting the desired tuning, and a JCheckBox for toggling the repeat function.
Provides toggleRepeat() for toggling the repeatEnabled field, a private field of the GuitarTunerGUI class.
I created a SwingWorker that is responsible for playing a MIDI note in a separate thread. This solves the issue of keeping the GUI responsive while the note is being played.
However, a problem arises when repeat is enabled and the user pushes more than one button.
When the user pushes one of the six JButtons the listener does the following:
public void actionPerformed(ActionEvent event) {
// The note param is a private field of the listener object
MusicianWorker clapton = new MusicianWorker(note);
clapton.execute();
}
The execute method does the following:
protected Void doInBackground() throws Exception {
do {
NotePlayer.playNote(thisNote);
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
}
} while (repeatEnabled);
return null;
}
An issue arises when the user pushes multiple buttons without toggling repeat. For example, when the 'A' button and the 'E' button are pushed sequentially, two threads are created and the 'A' and 'E' notes are both played repeatedly until repeatEnabled is toggled off. When the user pushes a JButton I need to first determine if any worker threads are currently executing and, if so kill those threads before playing the specified note. Thanks in advance for your time and feedback.
You need to maintain shared state between your workers. Introduce new boolean variable "playing". Before execution check whether playing flag is set to true, after execution set it to false again.
The code you have given is great, it just needs to be tweaked a little bit. When you create your SwingWorker, you should keep track of it in an instance variable (maybe in a List if you are going to be wanting to play multiple notes at some point?). Then, before playing a new note you check to see if the last note has finished, and if not, you cancel it.
Whether or not cancellation will have any effect on your MusicianWorker is up to you. The worker thread will be interrupted, which would mean that your Thread.sleep method would prematurely terminate if it is running - you would have to check your docs to see what effect it would have on NotePlayer.
Lastly, it seems that you don't actually need to be using the SwingWorker at all, since your background task is not interacting with the UI. You might want to investigate Executors.
You could try something like this:
public class AlbertHall {
private final ExecutorService es = Executors.newSingleThreadExecutor();
// No longer a local variable in the listener
private Future<Void> clapton; // ... or maybe a Collection of Futures
private class Listener implements ActionListener {
private final Note note;
public Listener(Note note) {
this.note = note;
}
public void actionPerformed(ActionEvent event) {
// Watch out, Clapton may finish after you have asked if he is done
// but before you call cancel
if (clapton != null && !clapton.isDone()) clapton.cancel(true);
// You may need to have a wait loop here if Clapton takes a while
// to leave the stage
// Next note
clapton = es.submit(new MusicianWorker(note));
}
}
static class MusicianWorker implements Runnable {
private final Note note;
public MusicianWorker(Note note) {
this.note = note;
}
public void run() {
boolean cancelRequested = false;
do {
NotePlayer.playNote(thisNote);
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
// Looks like we got cancelled
cancelRequested = true;
}
} while (repeatEnabled && !cancelRequested);
}
}
}

Java - Periodic updates from MySQL

I am developing a Scoreboard Java application for my work. It uses MySQL to store the score values and a Java application accesses them and displays them on a projector. So far I have managed to create a Java application using Swing. I display all of the scores using jLabels so that they can be updated without completely redrawing the scoreboard.
Now, I need to get the scoreboard to update periodically. I have attempted to use Thread.sleep but I don't know how to interrupt the thread. The reason I need to interrupt the thread is that if the number of entries to display on the scoreboard is changed on the config panel, the scoreboard must redraw in order to display the right nummber.
Currently sleep works fine in the code so long as I don't touch anything. But as soon as I change anything in the ConfigPanel things go awry.
package au.thewebeditor.scoreboard.apps;
import java.lang.*;
public class Program {
private static Scoreboard sb;
private static ConfigPanel cp;
public Program(){
sb = new Scoreboard();
cp = new ConfigPanel();
}
public static void redrawScoreboard() throws NullPointerException{
try{
sb.dispose();
} catch (NullPointerException e){
//DO NOTHING
}
sb = new Scoreboard();
try {
cp.toFront();
} catch (NullPointerException e) {
cp = new ConfigPanel();
}
constUpdates();
}
public static void showConfig(){
cp.setVisible(true);
cp.toFront();
}
public static void main(String[] arguments){
new Program();
constUpdates();
}
private static void constUpdates() {
boolean go = true;
while (go){
try {
Thread.sleep(5000);
Scoreboard.updateScores();
} catch (InterruptedException e) {
//DO nothing
}
}
}
}
When the connfiguration has been changed redrawScoreboard() is called.
At the moment, when redrawScoreboard is called it just sits in the queue while constUpdates keeps counting to 5000. How do I interrupt the sleep so I can redraw the scoreboard. Is sleep even the best option here? Or should I try something else?
you have an issue with Concurency in Swing, any create, update, modify Swing GUI must be done on Event Dispatch Thread, maybe reason for wrapping sb.dispose(); into try - catch by throws NullPointerException
Swing GUI must be created on Initial Thread
there no reason to recreate a new Top-Level Container every 5th. seconds, reuse JComponents added to contianer on app's start_up
use util.Timer to invoke SwingWorker,
Calling constUpdates takes the current thread an puts it in an infinite loop.
If the config UI is calling it it will put the UI thread in an infinite loop.
It'd be better just to have that loop in the main function.
Should you get an InteruptedException you should break out of the loop, not keep going.
You've a heady mix of static and non-static things, try to make it so that objects get passed arround instead.
If you want the config to ask the scoreboard to redraw, pass it the scoreboard so it can call redraw directly and leave the polling alone.

JTextArea text disappears

I'm making a chess program for a project. I'm trying to add a move history box to the side of the board. The move history works fine, and the data is properly sent to the text area, but the text inside the JTextArea disappears while the AI is thinking about his move.
public void aiMove(){
if (!playing){ return; }
paintImmediately(0,0,totalX,totalY);
ai = eve.getMove(chess,wtm,aiOut); //text disappears here
chess.makeMove(ai);
wtm = !wtm;
humanMove = true;
writeMove(ai); //updates move history, text reappears here
playing = stillPlaying();
repaint();
}
private void writeMove(Move move){
char c = "abcdefgh".charAt(7-move.fromY);
char h ="abcdefgh".charAt(7-move.toY);
String s = Character.toString(c)+(move.fromX+1)+" - "+Character.toString(h)+(move.toX+1)+" ";
if (!wtm){
String q = chess.getFullMove()+". "+s+" ";
moves.setText(moves.getText()+q);
}
else {
moves.setText(moves.getText()+s+"\n");
}
}
Here's a print screen of what's happening.
http://s13.postimage.org/mh7hltfk7/JText_Area_disappear.png
SOLVED
Thanks to all replies. I changed aiMove() so it creates a thread. Here is what I did.
Attempt #3... swing is still so foreign to me. I didn't want to change writeMove to getMove or I would have to rewrite the human's turn slightly. Since the project is essentially done, I am trying to avoid as much work as possible :)
The GUI is entirely optional anyways, I was just doing it for fun, and to try and learn a bit of swing.
public void aiMove(){
if (!playing){ return; }
if (!aiThread.isAlive()){
aiThread = new Thread(){
public void run(){
ai = eve.getMove(chess,wtm,aiOut);
chess.makeMove(ai);
wtm = !wtm;
humanMove = true;
SwingUtilities.invokeLater(new Runnable(){
public void run(){
writeMove(ai);
}
});
repaint();
playing = stillPlaying();
}
};
aiThread.start();
}
}
It also fixed a problem I had before, in that if I were to hold down the 'a' key (force ai move), it would queue up many forced ai moves. Now that doesn't happen.
The problem is your AI thinking is CPU intensive/time consuming, thus it is considered a long running task. You should not do long running tasks on GUI Event Dispatch Thread as this will cause the UI to seem frozen and thus only show updates after the task has finished.
Fortunately there are 2 different approaches you could use:
Use a Swing Worker which as the tutorial states:
The SwingWorker subclass can define a method, done, which is
automatically invoked on the event dispatch thread when the background
task is finished.
SwingWorker implements java.util.concurrent.Future.
This interface allows the background task to provide a return value to
the other thread. Other methods in this interface allow cancellation
of the background task and discovering whether the background task has
finished or been cancelled.
The background task can provide
intermediate results by invoking SwingWorker.publish, causing
SwingWorker.process to be invoked from the event dispatch thread.
The background task can define bound properties. Changes to these
properties trigger events, causing event-handling methods to be
invoked on the event dispatch thread.
Alternatively create separate Thread for AI thinking and wrap setText call in SwingUtilities.invokeLater(...);
Thread t=new Thread(new Runnable() {
#Override
public void run() {
}
});
t.start();
UPDATE
After reading MadProgrammers comment (+1 to it) please remember to create/manipulate your GUI/Swing components on EDT via the SwingUtilities.invokeLater(..) block. You can read more on it here.
UPDATE 2:
That edit is defeating the point, the only call on EDT in SwingUtilitites block should be the setText or atleast only code that manipulates a Swing component i.e
public void aiMove(){
if (!playing){ return; }
if (!aiThread.isAlive()){ //originally initialized by constructor
aiThread = new Thread(){
public void run(){
ai = eve.getMove(chess,wtm,aiOut);
chess.makeMove(ai);
wtm = !wtm;
humanMove = true;
SwingUtilities.invokeLater(new Runnable(){
public void run(){
writeMove(ai);
}
});
repaint();
playing = stillPlaying();
}
};
aiThread.start();
}
}

Categories

Resources