What We Will Cover
Illuminations
- You should have finished your project design by now
- By this week you should have completed the first implementation
^ top
14.1: Thread Fundamentals
Learner Outcomes
At the end of the lesson the student will be able to:
- Explain the basic difference between a program that runs in a single thread and a program that runs under multiple threads.
- Name three common reasons for using threads in a Java application.
- List the three Java API classes or interfaces that have methods related to threading.
- Use the
Thread class or the Runnable interface to create a thread.
- Explain the advantage of creating a thread by extending the
Runnable interface rather than by inheriting the Thread class.
- Use the
interrupt() method and the InterruptedException class to create a thread that can be interrupted.
|
^ top
14.1.1: About Threads
Thread: a single sequential flow of control within a program
- All Java programs run in one or more threads
- Using threads, you can define tasks that act independently of each other
- Each task can seemingly operate in parallel
- A thread scheduler determines which thread to run next
- Below is an example of threads operating in Java
- Start each of the applets by clicking on them with the mouse
- Each sort appears to be operating at the same time because of threads
| Bubble Sort |
Bi-Directional Bubble Sort |
Quick Sort |
|
|
|
|
- The program works because the thread scheduler allows each thread to run a short while
- After a short time, a context switch occurs that changes which thread is running
- Since context switches occur frequently, it appears that multiple tasks are happening at the same time
Typical Uses for Threads
- To give the appearance of parallel processing
- To make use of multiple processors and perform tasks in parallel
- Multiple-processor chips are becoming common
- When there are many tasks to perform and one is often waiting

- To improve the responsiveness of a user interface
- Allow time-consuming tasks to occur in the background
^ top
14.1.2: Classes and Interfaces for Working with Threads
- There are two main classes and one interface commonly used for working with threads:
| Class/Interface |
Description |
| Thread |
A class that defines a thread. |
| Runnable |
An interface that must be implemented by the class of any object that is going to be executed by a thread. |
| Object |
Several methods of the Object class are used for working with threads. |
- You can see the inheritance relationship of these classes and diagrams below:

- The
Runnable interface has just one method: run()
- Any thread calls the
run() method when it starts running
- In addition, threads end when they complete the
run() method
- Constructors and methods of the
Thread class are shown below
- We will cover these methods in the following sections
- In addition, methods of the
Object class used for threading are shown
- These methods are used for signal-wait synchronization
Commonly Used Constructors of the Thread Class
| Constructor |
Description |
| Thread() |
Constructs a new Thread object with default settings. |
| Thread(Runnable) |
Constructs a new Thread object from any object that implements the Runnable interface |
| Thread(String) |
Constructs a new Thread object with the specified name. |
| Thread(Runnable, String) |
Constructs a new Thread object with the specified name from any object that implements the Runnable interface. |
Commonly Used Methods of the Thread Class
| Method |
Description |
| run() |
Called by the thread scheduler to run the thread. All subclasses of Thread should override this method. |
| start() |
Called to start the execution of the thread. |
| currentThread() |
A static method that returns a reference to the currently executing thread. |
| sleep(long millis) |
A static method that stops the current thread from executing for the specified number of milliseconds. |
| yield() |
Causes the currently executing thread object to temporarily pause and allow other threads to execute. |
| getName() |
Returns the name of the thread. |
| getState() |
Returns the state of the thread. |
| interrupt() |
Interrupts this thread. |
| isInterrupted() |
Tests whether this thread has been interrupted. |
| join() |
Waits for the thread on which it is called to finish and join the calling thread. |
| setPriority(int) |
Changes the priority of the thread. |
Methods of the Object Class Used for Threading
| Method |
Description |
| wait() |
Causes current thread to wait until another thread invokes the notify() method or the notifyAll() method for the current object. |
| notify() |
Wakes up a single arbitrary thread that is waiting on this object's monitor. |
| notifyAll() |
Wakes up all threads that are waiting on this object's monitor. |
^ top
14.1.3: Using the sleep() Method
Example of Simple Animation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
// Unresponsive GUI
public class Bubbles extends JFrame
implements ActionListener {
public final static int X_LOC = 100, Y_LOC = 100,
WIDTH = 400, HEIGHT = 300;
public static final int DELAY = 100;
public static final int COLORS = 16581375;
public static final int MAX_SIZE = 40;
public static final int NUM_SHAPES = 400;
private JButton startButton;
private JButton stopButton;
private JPanel canvas;
public static void main(String[] args) {
new Bubbles();
}
public Bubbles() {
super("Bubbles Animation");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container pane = getContentPane();
canvas = new JPanel();
pane.add(canvas, BorderLayout.CENTER);
startButton = new JButton("Start");
stopButton = new JButton("Stop");
JPanel panel = new JPanel();
panel.add(startButton);
startButton.addActionListener(this);
panel.add(stopButton);
stopButton.addActionListener(this);
pane.add(panel, BorderLayout.SOUTH);
setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
setVisible(true);
}
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if (source == startButton) {
draw();
} else if (source == stopButton) {
System.out.println("Goodbye!");
System.exit(0);
}
}
public void draw() {
Graphics g = canvas.getGraphics();
Color c;
for (int i = 0; i < NUM_SHAPES; i++) {
int x1 = (int)(Math.random() * WIDTH);
int y1 = (int)(Math.random() * HEIGHT);
int size = (int)(Math.random() * MAX_SIZE);
c = new Color((int)(Math.random() * COLORS));
g.setColor(c);
g.drawOval(x1, y1, size, size);
pause(DELAY);
}
}
public void pause(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
System.exit(0);
}
}
}
|
^ top
14.1.4: Subclassing Class Thread
- When we run the previous example, we notice that we cannot use any of the controls until the animation is finished
- To fix this nonresponsive interface, we can use a thread to run the animation in the background
- This will free up the GUI thread and allow us to use the controls during the animation
- There are two ways to implement a thread
- Subclass
Thread and override the run method
- Implement the
Runnable interface
- The most straightforward way to implement threads is to subclass the
Thread class
- We implement the
Thread subclass as an inner class to keep our animation class self-contained
private class Bubbler extends Thread
- The
Thread class implements the run() method with an empty body:
public void run() { }
- Thus any subclass of
Thread is expected to override the run() method
- We put our animation code in the
run() method since that is where the thread runs:
public void run() {
// animation code
}
- Note that we do not call
run() directly
- Instead, to start the thread, we call the
start() method
Bubbler bub = new Bubbler();
bub.start();
- The JVM calls
run() after we call the start() method
- This allows the JVM to set up the internal data structures needed to handle the thread
- When
start() is called, the thread starts executing independently
Example of Simple Animation Subclassing Thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
// Multithreaded GUI
public class Bubbles2 extends JFrame
implements ActionListener {
public final static int X_LOC = 100, Y_LOC = 100,
WIDTH = 400, HEIGHT = 300;
public static final int DELAY = 100;
public static final int COLORS = 16581375;
public static final int MAX_SIZE = 40;
public static final int NUM_SHAPES = 400;
private JButton startButton;
private JButton stopButton;
private JPanel canvas;
public static void main(String[] args) {
new Bubbles2();
}
public Bubbles2() {
super("Bubbles Animation");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container pane = getContentPane();
canvas = new JPanel();
pane.add(canvas, BorderLayout.CENTER);
startButton = new JButton("Start");
stopButton = new JButton("Stop");
JPanel panel = new JPanel();
panel.add(startButton);
startButton.addActionListener(this);
panel.add(stopButton);
stopButton.addActionListener(this);
pane.add(panel, BorderLayout.SOUTH);
setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
setVisible(true);
}
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if (source == startButton) {
Bubbler bub = new Bubbler();
bub.start();
} else if (source == stopButton) {
System.out.println("Goodbye!");
System.exit(0);
}
}
private class Bubbler extends Thread {
public void run() {
Graphics g = canvas.getGraphics();
Color c;
for (int i = 0; i < NUM_SHAPES; i++) {
int x1 = (int)(Math.random() * WIDTH);
int y1 = (int)(Math.random() * HEIGHT);
int size = (int)(Math.random() * MAX_SIZE);
c = new Color((int)(Math.random() * COLORS));
g.setColor(c);
g.drawOval(x1, y1, size, size);
pause(DELAY);
}
}
public void pause(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
System.exit(0);
}
}
}
}
|
^ top
14.1.5: Implementing Interface Runnable
Example of Simple Animation Implementing Runnable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
// Multithreaded runable GUI
public class Bubbles3 extends JFrame
implements ActionListener, Runnable {
public final static int X_LOC = 100, Y_LOC = 100,
WIDTH = 400, HEIGHT = 300;
public static final int DELAY = 100;
public static final int COLORS = 16581375;
public static final int MAX_SIZE = 40;
public static final int NUM_SHAPES = 400;
private JButton startButton;
private JButton stopButton;
private JPanel canvas;
public static void main(String[] args) {
new Bubbles3();
}
public Bubbles3() {
super("Bubbles Animation");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container pane = getContentPane();
canvas = new JPanel();
pane.add(canvas, BorderLayout.CENTER);
startButton = new JButton("Start");
stopButton = new JButton("Stop");
JPanel panel = new JPanel();
panel.add(startButton);
startButton.addActionListener(this);
panel.add(stopButton);
stopButton.addActionListener(this);
pane.add(panel, BorderLayout.SOUTH);
setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
setVisible(true);
}
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if (source == startButton) {
Thread runner = new Thread(this);
runner.start();
} else if (source == stopButton) {
System.out.println("Goodbye!");
System.exit(0);
}
}
public void run() {
Graphics g = canvas.getGraphics();
Color c;
for (int i = 0; i < NUM_SHAPES; i++) {
int x1 = (int)(Math.random() * WIDTH);
int y1 = (int)(Math.random() * HEIGHT);
int size = (int)(Math.random() * MAX_SIZE);
c = new Color((int)(Math.random() * COLORS));
g.setColor(c);
g.drawOval(x1, y1, size, size);
pause(DELAY);
}
}
public void pause(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
System.exit(0);
}
}
}
|
^ top
14.1.6: Stopping Threads
Why Use Volatile?
- Threads can have local copies of variables to improve performance
- The value of the local variable can be different than the "correct" value of the main memory variable
- This is not a problem unless another thread needs to access the variable
- Since we want other threads to be able to stop the animation thread, we need to ensure all threads access the same variable
- Using
volatile prohibits a variable from being copied to a thread's local memory
- Thus changes to that variable by other threads will be seen by the animation thread
Interrupting a Thread
- If your thread waits for long periods, the
Thread class has an interrupt() method to wake the thread
- Calling the
interrupt() method either:
- Sets the status of the thread to interrupted
- Throws an exception if the thread is blocked (not runnable)
- To check the interrupt status, you use the method
isInterrupted()
- However, if the thread is sleeping (blocked), it cannot execute the code that sets the interrupted status
- Instead, the thread clears the interrupt status and throws an
InterruptedException
- Thus we must consider both cases and implement code to:
- Stop a thread while executing
- Stop a thread if blocked (not executing)
- To do this, we create a boolean instance variable named
done
private volatile boolean done;
- We redefine the Stop button so that whenever it is pressed we set the
stop variable to true
if (source == stopButton) {
if (runner != null) {
done = true; // first set variable
runner.interrupt(); // then interrupt
}
}
- Since the Stop button can be pressed without pressing the Start button, we protect against this case with an
if statement as shown
- Next we code the
run() method to check for a stop condition each time through its loop:
for (int i = 0; i < NUM_SHAPES && !done; i++)
- In addition to these exceptions, there are others that may be thrown.
- For our simple application, however, we will let these exceptions be caught by the general exception handler
- The complete example is shown below
Example of Simple Animation With Stop Enabled
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
// Stopable GUI
public class Bubbles4 extends JFrame
implements ActionListener, Runnable {
public final static int X_LOC = 100, Y_LOC = 100,
WIDTH = 400, HEIGHT = 300;
public static final int DELAY = 100;
public static final int COLORS = 16581375;
public static final int MAX_SIZE = 40;
public static final int NUM_SHAPES = 400;
private JButton startButton;
private JButton stopButton;
private JPanel canvas;
private Thread runner;
private volatile boolean done;
public static void main(String[] args) {
new Bubbles4();
}
public Bubbles4() {
super("Bubbles Animation");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container pane = getContentPane();
canvas = new JPanel();
pane.add(canvas, BorderLayout.CENTER);
startButton = new JButton("Start");
stopButton = new JButton("Stop");
JPanel panel = new JPanel();
panel.add(startButton);
startButton.addActionListener(this);
panel.add(stopButton);
stopButton.addActionListener(this);
pane.add(panel, BorderLayout.SOUTH);
setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
setVisible(true);
}
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if (source == startButton) {
done = false;
runner = new Thread(this);
runner.start();
} else if (source == stopButton) {
if (runner != null) {
done = true; // first set variable
runner.interrupt(); // then interrupt
}
}
}
public void run() {
Graphics g = canvas.getGraphics();
Color c;
for (int i = 0; i < NUM_SHAPES && !done; i++) {
int x1 = (int)(Math.random() * WIDTH);
int y1 = (int)(Math.random() * HEIGHT);
int size = (int)(Math.random() * MAX_SIZE);
c = new Color((int)(Math.random() * COLORS));
g.setColor(c);
g.drawOval(x1, y1, size, size);
pause(DELAY);
}
System.out.println("Finished making bubbles!");
}
public void pause(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
}
}
}
|
More Information
^ top
14.1.7: Summary
- A thread is a single flow of control through a program
- Java allows the programmer to write programs with multiple threads
- Using threads, you can define tasks that act independently of each other
- A thread scheduler determines which thread to run next
- Multithreading is typically used to:
- To give the appearance of parallel processing
- To make use of multiple processors and perform tasks in parallel
- When there are many tasks to perform and one is often waiting
- To improve the responsiveness of a user interface
- You can create a thread by either:
- Extending the
Thread class and instantiating the new class
- Implementing the
Runnable interface and passing a reference of the object to a constructor of the Thread class
- To start a thread, you call the
start() method of the Thread class
- You never call the
run() method directly
- Stopping a thread is more difficult than starting one
- The way to end a thread is to finish its run() method
- Each thread is responsible for checking itself periodically to see if it should end
- You can use methods
interrupt() and isInterrupted() to help with this task
- However, if a thread is blocked, it cannot execute the code that sets the interrupted status
- Instead, the thread clears the interrupt status and throws an
InterruptedException
- Thus, you must write code that works both if the thread is running and if the thread is blocked
Check Yourself
- What is a thread?
- What are three typical reasons for using threads? (answer)
- What is the basic difference between a program that runs in a single thread and a program that runs under multiple threads?
- What two techniques can you use to create a thread?
- How do you start a thread?
- How do you use the
sleep() method of the Thread class?
- How can you tell which thread will run next?
- How do you stop a thread that is running?
^ top
Exercise 14.1
Take one minute to review the Check Yourself questions. We will discuss the questions as time permits.
^ top
14.2: Simple Animation
Learner Outcomes
At the end of the lesson the student will be able to:
- Describe how computer animations are generated
- Write code to load images into a list
- Write code to display a sequence of images
|
^ top
14.2.1: Introduction to Computer Animation
- Let us look at a simple use of threads for computer animation
- Animation is the illusion of motion created by displaying a series of images
- For example, the following animation displays at 10 frames per second (FPS)

- The speed of the display is fast enough that you cannot easily see the individual frames
- Contrast this with the following image that displays at 2 frames per second

- At 2 FPS, the animation is slow enough that you can see the individual frames
- Both of these animations were produced by displaying these images, known as frames:

- Note that these images are in the public domain and were obtained from Wikipedia
Creating an Animation Loop
- To create an animation, we use an animation loop
- There are three steps to an animation loop as shown in the following diagram:

- During the update portion of the loop, you calculate the position of your shape
- During the render portion, you draw the shape
- Then you wait a short while before repeating the process
- There are two reasons for waiting before repeating:
- To slow down the animation's frame rate
- To allow other parts of the program to run
- The second reason is important but not always obvious
- Whenever you create a thread, you need to stop running from time-to-time
- Otherwise, especially on a single-processor system, other threads may not get a chance to run
- To prevent starvation, a thread needs to pass control to other threads from time to time
- The best time is when the current thread does not need to run
^ top
14.2.2: Coding an Animation
- As an example of an animation, we can draw a circle and move it around the graphics window
- Recall that to draw a simple filled circle we can use:
graphicsObj.fillOval(x, y, width, height);
- Where:
- graphicsObj: the name of the graphics object
- x: the x coordinate of the upper left corner
- y: the y coordinate of the upper left corner
- y: the width of the oval
- y: the height of the oval
- All the measurements are in pixels from the upper left-hand corner of the screen
- If we make the width and height the same, we have a circle
- For example:
g.fillOval(x, y, DIAMETER, DIAMETER);
- To create movement, we need to change the x and y coordinates over time
- We control how much to change the location using two variables:
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
- To keep our calculations accurate, we use floating-point numbers:
private float x; // x position in pixels
private float y; // y position in pixels
- Every time we want to move our shape, we update the postiion using code like:
x += dx;
y += dy;
- We will need to round the
float variables to type int, which is easy with Math.round()
- We can use a static import (see lesson 5.1.4) to shorten the method calls in the
Math package:
import static java.lang.Math.*;
- Thus our call to
fillOval() will look like:
g.fillOval(round(x), round(y), DIAMETER, DIAMETER);
- The following code shows a complete animation example
- The animation loop is in the
run() method
- Within the loop are calls to:
update(): updates the position and state of shapes
repaint(): calls paintComponent() to render the shapes
Thread.sleep(DELAY): pauses the loop for DELAY milliseconds
- To start the animation thread, we call the
startAnimation() method
- To stop the animation, we call the
stopAnimation() method
Example Animation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
import static java.lang.Math.*;
import java.awt.*;
import javax.swing.*;
public class SimpleAnimation1 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33;
private Thread runner; // for animation
private volatile boolean running; // stop animation
// Ball variables
public static final int DIAMETER = 27;
private float x; // x position in pixels
private float y; // y position in pixels
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation1 sa = new SimpleAnimation1();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
}
public SimpleAnimation1() {
setBackground(Color.WHITE);
y = 100;
dx = 3.5f;
dy = 0;
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
x += dx;
y += dy;
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
g.setColor(Color.RED);
g.fillOval(round(x), round(y), DIAMETER, DIAMETER);
}
}
|
^ top
14.2.3: More About Starting and Stopping the Animation
Stopping the Animation
^ top
14.2.4: Bouncing off the Walls
- To make our animation more interesting, we can bounce the ball off the walls
- Imitating a real thing or process, like a bouncing ball, is known as a simulation
- One use of computers is simulating, or modeling, key characteristic of systems
- For our simulation, we use the sides, top and bottom of the window as the walls
- Remember that our coordinate system in the upper left-hand area of the window:

Example Animation With Bounce
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
import static java.lang.Math.*;
import java.awt.*;
import javax.swing.*;
public class SimpleAnimation2 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33;
private Thread runner; // for animation
private volatile boolean running; // stop animation
// Ball variables
public static final int DIAMETER = 27;
private float x; // x position in pixels
private float y; // y position in pixels
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation2 sa = new SimpleAnimation2();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
}
public SimpleAnimation2() {
setBackground(Color.WHITE);
dx = (float) random() * 2 + 3;
dy = (float) random() * 2 + 3;
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
if (x < 0) {
dx = abs(dx);
} else if (x + DIAMETER > getWidth()) {
dx = -abs(dx);
}
if (y < 0) {
dy = abs(dy);
} else if (y + DIAMETER > getHeight()) {
dy = -abs(dy);
}
x += dx;
y += dy;
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
g.setColor(Color.RED);
g.fillOval(round(x), round(y), DIAMETER, DIAMETER);
}
}
|
^ top
14.2.5: Animating Two Objects
- If we want to animate two shapes, we need separate variables for each object
- As we add more shape objects, our code in the animation loop becomes more cluttered
- To avoid the clutter and duplication, we can encapsulate the code for the shape in a class
- Here is a
Ball class that encapsulates the information for the moving shape
- Following the
Ball class is an animation application bouncing two balls
- Notice how simple the
update() and paintComponent() methods remain
- Also notice how we pass a reference to the drawing area to the Ball class
- This allows resizing the drawing area while the application is running
- In addition, we pass a Color argument to the Ball class so it can draw its own color
Ball Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import static java.lang.Math.*;
import java.awt.*;
public class Ball {
public static final int DIAMETER = 27;
private float x; // x position in pixels
private float y; // y position in pixels
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
private Component canvas;
private Color color;
public Ball(Component panel, Color c) {
canvas = panel;
color = c;
dx = (float) random() * 2 + 3;
dy = (float) random() * 2 + 3;
}
// Update state
public void update() {
if (x < 0) {
dx = abs(dx);
} else if (x + DIAMETER > canvas.getWidth()) {
dx = -abs(dx);
}
if (y < 0) {
dy = abs(dy);
} else if (y + DIAMETER > canvas.getHeight()) {
dy = -abs(dy);
}
x += dx;
y += dy;
}
// Render
public void draw(Graphics g) {
g.setColor(color);
g.fillOval(round(x), round(y), DIAMETER, DIAMETER);
}
}
|
Example Animation with Two Balls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
import java.awt.*;
import javax.swing.*;
public class SimpleAnimation3 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33;
private Thread runner; // for animation
private volatile boolean running; // stop animation
// Ball variables
private Ball b1;
private Ball b2;
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation3 sa = new SimpleAnimation3();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
}
public SimpleAnimation3() {
setBackground(Color.WHITE);
b1 = new Ball(this, Color.RED);
b2 = new Ball(this, Color.BLUE);
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
b1.update();
b2.update();
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
b1.draw(g);
b2.draw(g);
}
}
|
^ top
14.2.6: Animating Many Objects
- We can take our animation one step further and animate many objects
- To juggle several balls at once we use a list, such as an array or
ArrayList, with a counting loop
- You can see the
ArrayList and loops in the following example
Example Animation with Many Balls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class SimpleAnimation4 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33; // 30 FPS
private Thread runner; // for animation
private volatile boolean running; // stop animation
// Ball variables
private static final int NUM_BALLS = 10;
private ArrayList<Ball> balls;
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation4 sa = new SimpleAnimation4();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
}
public SimpleAnimation4() {
setBackground(Color.WHITE);
Random r = new Random();
balls = new ArrayList<Ball>();
for (int i = 0; i < NUM_BALLS; i++) {
int red = r.nextInt(255);
int green = r.nextInt(255);
int blue = r.nextInt(255);
Color c = new Color(red, green, blue);
Ball b = new Ball(this, c);
balls.add(b);
}
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
for (int i = 0; i < balls.size(); i++) {
Ball b = balls.get(i);
b.update();
}
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
for (int i = 0; i < balls.size(); i++) {
Ball b = balls.get(i);
b.draw(g);
}
}
}
|
^ top
14.2.7: Animating Images
- We can move images on the screen rather than a shape
- To move images, we need to:
- Load images from a file into a suitable object
- Render the image in the animation loop
- A good choice for storing an image in an object is
BufferedImage
BufferedImage is a subclass of Image, and therefore you can use it in place of Image
- Many methods of the Java API use either an Image or BufferedImage
Loading a BufferedImage from a File
Loading Images from JAR Files
- A JAR file is a way of packaging code and resources together into a single, compressed file
- Resources can be almost anything, including images and sounds
- To load images from a JAR, you need to modify how the image is loaded:
BufferedImage im =
ImageIO.read(getClass().getResource(fileName));
- To make loading a
BufferedImage easy, we can write a loadImage() method like:
private BufferedImage loadImage(String fileName) {
BufferedImage im = null;
try {
im = ImageIO.read(getClass().getResource(fileName));
} catch (IOException e) {
System.out.println("Error loading " + fileName);
}
return im;
}
- Note the use of the
try-catch statement
- You need the
try-catch statement because reading the image file can cause an error
- For instance, if you try to read a file that does not exist
- Note that this code will load from either a JAR or a regular file
- Thus you might as well use the latter code to allow you the most flexibility storing your images
Rendering the Image
Ball Class to Handle Images
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import static java.lang.Math.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class Ball2 {
private float x; // x position in pixels
private float y; // y position in pixels
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
private Component canvas;
private BufferedImage im;
public Ball2(Component panel, BufferedImage image) {
canvas = panel;
im = image;
dx = (float) random() * 2 + 3;
dy = (float) random() * 2 + 3;
}
// Update state
public void update() {
if (x < 0) {
dx = abs(dx);
} else if (x + im.getWidth() > canvas.getWidth()) {
dx = -abs(dx);
}
if (y < 0) {
dy = abs(dy);
} else if (y + im.getHeight() > canvas.getHeight()) {
dy = -abs(dy);
}
x += dx;
y += dy;
}
// Render
public void draw(Graphics g) {
g.drawImage(im, round(x), round(y), null);
}
}
|
Example Animation with Images
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SimpleAnimation5 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33; // 30 FPS
private Thread runner; // for animation
private volatile boolean running; // stop animation
// Ball variables
private static final int NUM_BALLS = 10;
private ArrayList<Ball2> balls;
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation5 sa = new SimpleAnimation5();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
}
public SimpleAnimation5() {
setBackground(Color.WHITE);
balls = new ArrayList<Ball2>();
BufferedImage im = loadImage("ball.gif");
for (int i = 0; i < NUM_BALLS; i++) {
Ball2 b = new Ball2(this, im);
balls.add(b);
}
}
private BufferedImage loadImage(String fileName) {
BufferedImage im = null;
try {
im = ImageIO.read(getClass().getResource(fileName));
} catch (IOException e) {
System.out.println("Error loading " + fileName);
}
return im;
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
for (int i = 0; i < balls.size(); i++) {
Ball2 b = balls.get(i);
b.update();
}
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
for (int i = 0; i < balls.size(); i++) {
Ball2 b = balls.get(i);
b.draw(g);
}
}
}
|
^ top
14.2.8: Controlling the Animation
- We can add controls to our animation with either GUI components or low-level keyboard and mouse event handing
- We will show an example using keyboard and mouse event handling
- The same principles apply when using components
Keyboard Control
- We add the code to stop and exit the animation when the user presses the Escape key
- To handle keyboard events, we use an inner class:
private class KeyHandler extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ESCAPE) {
stopAnimation();
System.out.println("Goodbye!");
System.exit(0); // so enclosing JFrame exits
}
}
}
- To listen for keyboard events, we register a
KeyHandler in the constructor of our animation class:
addKeyListener(new KeyHandler());
- One VERY IMPORTANT step is to request focus for the animation panel:
sa.requestFocusInWindow(); // After visible
- Without focus, the animation panel does not hear the keyboard
Mouse Control
Example Animation with Keyboard and Mouse Control
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SimpleAnimation6 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33; // 30 FPS
private Thread runner; // for animation
private volatile boolean running; // stop animation
// Ball variables
private static final int NUM_BALLS = 10;
private ArrayList<Ball2> balls;
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation6 sa = new SimpleAnimation6();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
// Needed for keys to hear
sa.requestFocusInWindow(); // After visible
}
public SimpleAnimation6() {
addKeyListener(new KeyHandler());
addMouseListener(new MouseHandler());
setBackground(Color.WHITE);
balls = new ArrayList<Ball2>();
BufferedImage im = loadImage("ball.gif");
for (int i = 0; i < NUM_BALLS; i++) {
Ball2 b = new Ball2(this, im);
balls.add(b);
}
}
private BufferedImage loadImage(String fileName) {
BufferedImage im = null;
try {
im = ImageIO.read(getClass().getResource(fileName));
} catch (IOException e) {
System.out.println("Error loading " + fileName);
}
return im;
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
for (int i = 0; i < balls.size(); i++) {
Ball2 b = balls.get(i);
b.update();
}
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
for (int i = 0; i < balls.size(); i++) {
Ball2 b = balls.get(i);
b.draw(g);
}
}
private class KeyHandler extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ESCAPE) {
stopAnimation();
System.out.println("Goodbye!");
System.exit(0); // so enclosing JFrame exits
}
}
}
private class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
if (running) {
stopAnimation();
} else {
startAnimation();
}
}
}
}
|
^ top
14.2.9: Summary
- In this section we looked at how to create computer animation
- Animation is the illusion of motion created by displaying a series of images or shapes:

- To control the movement, we created an animation loop as shown in the following diagram:

- During the update portion of the loop, we calculated the position of our shape
- During the render portion, we drew the shape
- Then we waited a short while using
Thread.sleep() to slow down the animation and prevent resource starvation
- Computer animation draws the images or shapes at a specific place on your computer screen
- As an example of animation, we drew a filled oval to simulate a bouncing ball:
g.fillOval(x, y, DIAMETER, DIAMETER);
- To create movement we used two variables:
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
- During the update portion of the animation, we calcuated a new position using:
x += dx;
y += dy;
- To better simulate a ball, we added code to test for "walls" (the edge of the drawing panel)
- When the ball hit a wall, we reversed the dx- and dy-directions
- If we add balls to the animation, the code in the animation loop becomes cluttered
- To avoid the clutter, we created a
Ball class to encapsulate the location, speed and behavior of a ball
- We then looked at how to add several balls to the animation using a vector
- In addition, we looked at how to animate images by:
- Loading images from a file into a
BufferedImage object
- Rendering the image in the animation loop
- Finally, we looked at how to control the image using keyboard and mouse controls
Check Yourself
- What is animation?
- What is meant by the term "animation loop"?
- What three operations are present in an animation loop?
- What code did we use to calculate position from one animation frame to the next?
- True or false? To reduce clutter, you can encapsulate the animated object into a class.
- What two programming constructs allow you to easily animate multiple objects?
- What object is used to store images loaded from a file?
^ top
Exercise 14.2
Take one minute to review the Check Yourself questions. We will review the questions as time permits.
^ top
14.3: Playing Sound Files
Learner Outcomes
At the end of the lesson the student will be able to:
- Describe how sound is recorded in a digital format
- Discuss the approaches you can use in Java to play sounds
- Play sounds using the Java Sound API
|
^ top
14.3.1: Sound Basics
- In this lesson we discuss how to play sound files in Java
- Sound is a vibration through some medium transmitted as a wave
- Faster vibrations create a higher sound frequency and you hear a higher pitch
- The pressure of the sound wave creates an amplitude that makes sound louder or softer
- This is shown in the following diagram:

- While sound begins as a series of waves, it can be converted to a digital format

- Digital sound, or audio, is a series of discrete samples of the sound waves
- The amount of samples stored per second is called the sample rate
- CD audio, for instance, has a sample rate of 44.1 kHz
- In general, higher sampling rates give a more accurate audio representation but also larger files sizes
- The number of bits used to store the sample determines the number of variations in amplitude
- If a sample is 16 bits, it has 65,536 possible amplitudes
^ top
14.3.2: The Java Sound API
- Java has several different approaches to sound:
Applet play() method
AudioClip class
- Java Sound API
- Java Media Framework
- A flexible approach is the Java Sound API, which is what we will focus on
- The Java Sound API has two main parts:
- We will start with sampled audio and discuss MIDI later
Sampled Audio
- Java Sound can play sound formats with either 8- or 16-bit samples with sample rates from 8kHz to 48kHz
- Also, it can play either mono or stereo sound
- Additionally, you can install other format readers from third parties, such as an OGG decoder
- Java Sound provides support for reading three sampled sound file formats: AIFF, AU, and WAV
- All formats are very flexible, and it does not matter much which one you use
- Below is an overview of the a typical audio architecture (image from the Java Tutorial):

Getting Sounds
- To play sampled sound, you will need some sound files
- You can get free sound effects files from the Internet like:
- However, make sure you verify the licensing
- Also you can create your own sounds using sound programs on your computer
- Some free sound editing programs you might try are:
More Information
^ top
14.3.3: Opening a Sound File
- To load a sound file, you can use the
AudioSystem class
- The class has several static methods, of which we can use the
getAudioInputStream() to open an audio stream
- The
getAudioInputStream() method is overloaded so you can open a sound stream from many different sources
- For instance, you can open sound streams from files, URLs and other streams
- The
getAudioInputStream() methods return an AudioInputStream object, which we can use to read sound samples
- An
AudioInputStream also has a method getFormat() that returns an AudioFormat object
- The AudioFormat class lets us get information about the format of the sounds such as:
- Sample rate
- Number of channels
- Frame size
- Frame size is the number of bytes required for every sample for every channel
- For instance, 16-bit stereo sound has a frame size of 4 (2 bytes x 2 channels
- The frame size is useful for calculating how many bytes it takes to store a sound in memory
- For example, a 3-second sound with an audio format of 16-bit samples, stereo, 44.1kHz is:
3 x 2 x 2 44,100 = 517KB
- You could cut the size in half using mono sound instead of stereo
Example Code to Open a Sound File
More Information
^ top
14.3.4: Using a Line to Play a Sound Clip
- After opening a sound stream we need to send it to the sound system
- For this we send the sound stream to a
Line
- A
Line is an interface to send or receive audio to or from the sound system
- The
Line interface has several subinterfaces, including the SourceDataLine
- Here is the inheritance hierarchy for
Line:

- A
SourceDataLine lets you write to the sound system
- We create a
Line by using the getLine() method of the AudioSystem class
- We pass this method a
Line.Info object, which specifies the type of Line you want to create
DataLine.Info is a subclass of Line.Info that we can use to create the Line
Clips
- Another
Line subinterface is a Clip
- A
Clip is convenient because it loads samples into memory and feeds them to the audio system automatically
- The code to load a clip is shown below
- Though a
Clip is useful, it does have drawbacks
- Java Sound limits you to a maximum of 32 open lines at one time
- Since a
Clip is a Line, this means you can open only a limited number of sounds
- Even before we play any of them
- Several clips can play at once, but each
Clip can play only one sound at a time
- For instance, if we want two or three explosions to play at one time, we need a separate
Clip for each one
- If we keep our sound requirements moderately simple, we can usually get by just using clips
Example Code to Play a Clip
^ top
14.3.5: Example Playing a Sound
- Let us put the code snippets we saw above into a working program
- We can use our previous animation to play the sounds
- To give us a sound to play, we can use the following file:
- The example code assumes the sound file is placed in a subdirectory named "sounds"
Updating the Animation Class
- First we add a
Clip to the animation class:
private Clip clip;
- Then in the constructor, we open the clip:
try {
File file = new File("sounds/blip.wav");
AudioInputStream stream =
AudioSystem.getAudioInputStream(file);
clip = AudioSystem.getClip();
clip.open(stream);
} catch (IOException ex) {
ex.printStackTrace();
} catch (UnsupportedAudioFileException ex) {
ex.printStackTrace();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
}
- Then we add a
playSound() method we can call to produce the sound:
public void playSound() {
clip.setFramePosition(0); // Allows replay
clip.start();
}
- You can see the complete code listing below
Updating the Ball Class
- In addition to updating the animation class, we need to update the ball class as well
- We want to play a sound whenever the ball changes direction
- First we need a reference to the animation object so we can call the
play() method:
private SimpleAnimation7 anim;
- We can pass the reference in the constructor of the ball class:
public Ball3(SimpleAnimation7 panel, BufferedImage image) {
anim = panel;
// more code here
}
- Whenever the ball changes direction, we call the
playSound() method of the animation class:
anim.playSound();
- You can see the complete code listing below
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import javax.imageio.ImageIO;
import javax.sound.sampled.*;
import javax.swing.*;
public class SimpleAnimation7 extends JPanel
implements Runnable {
public static final int X_LOC = 100, Y_LOC = 100,
WIDTH = 600, HEIGHT = 400;
public static final int DELAY = 33; // 30 FPS
private Thread runner; // for animation
private volatile boolean running; // stop animation
private Clip clip;
// Ball variables
private static final int NUM_BALLS = 2;
private ArrayList<Ball3> balls;
public static void main(String[] args) {
JFrame frame = new JFrame("Animation Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
SimpleAnimation7 sa = new SimpleAnimation7();
frame.add(sa);
frame.setBounds(X_LOC, Y_LOC, WIDTH, HEIGHT);
frame.setVisible(true);
sa.startAnimation();
// Needed for keys to hear
sa.requestFocusInWindow(); // After visible
}
public SimpleAnimation7() {
addKeyListener(new KeyHandler());
addMouseListener(new MouseHandler());
setBackground(Color.WHITE);
balls = new ArrayList<Ball3>();
BufferedImage im = loadImage("images/ball.gif");
for (int i = 0; i < NUM_BALLS; i++) {
Ball3 b = new Ball3(this, im);
balls.add(b);
}
// Add sounds
try {
File file = new File("sounds/blip.wav");
AudioInputStream stream =
AudioSystem.getAudioInputStream(file);
clip = AudioSystem.getClip();
clip.open(stream);
} catch (IOException ex) {
ex.printStackTrace();
} catch (UnsupportedAudioFileException ex) {
ex.printStackTrace();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
}
}
private BufferedImage loadImage(String fileName) {
BufferedImage im = null;
try {
im = ImageIO.read(getClass().getResource(fileName));
} catch (IOException e) {
System.out.println("Error loading " + fileName);
}
return im;
}
// Initialise and start the animation
public void startAnimation() {
if (runner == null || !running) {
runner = new Thread(this);
runner.start();
}
}
// Called to stop execution
public void stopAnimation() {
running = false;
}
// Repeatedly update, render, sleep
public void run() {
running = true;
while (running) {
update(); // update position
repaint(); // render
try {
Thread.sleep(DELAY); // pause
} catch(InterruptedException ie) {
running = false;
}
}
}
// Update the animation state
public void update() {
for (int i = 0; i < balls.size(); i++) {
Ball3 b = balls.get(i);
b.update();
}
}
// Render the animation
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
for (int i = 0; i < balls.size(); i++) {
Ball3 b = balls.get(i);
b.draw(g);
}
}
// Play sound clip
public void playSound() {
clip.setFramePosition(0);
clip.start();
}
private class KeyHandler extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ESCAPE) {
stopAnimation();
System.out.println("Goodbye!");
System.exit(0); // so enclosing JFrame exits
}
}
}
private class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
if (running) {
stopAnimation();
} else {
startAnimation();
}
}
}
}
|
Class Ball3 that Calls the playSound() Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
import static java.lang.Math.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class Ball3 {
private float x; // x position in pixels
private float y; // y position in pixels
private float dx; // delta x in pixels per loop
private float dy; // delta y in pixels per loop
private SimpleAnimation7 anim;
private BufferedImage im;
public Ball3(SimpleAnimation7 panel, BufferedImage image) {
anim = panel;
im = image;
dx = (float) random() * 2 + 3;
dy = (float) random() * 2 + 3;
}
// Update state
public void update() {
if (x < 0) {
dx = abs(dx);
anim.playSound();
} else if (x + im.getWidth() > anim.getWidth()) {
dx = -abs(dx);
anim.playSound();
}
if (y < 0) {
dy = abs(dy);
anim.playSound();
} else if (y + im.getHeight() > anim.getHeight()) {
dy = -abs(dy);
anim.playSound();
}
x += dx;
y += dy;
}
// Render
public void draw(Graphics g) {
g.drawImage(im, round(x), round(y), null);
}
}
|
^ top
14.3.6: Summary
- Sound is a vibration through some medium as a wave
- Faster vibrations create a higher sound frequency and you hear a higher pitch
- The pressure of the sound wave creates an amplitude that makes sound louder or softer
- While sound begins as a series of waves, it can be converted to a digital format

- Digital audio is a series of discrete samples of the sound waves
- The sample rate of the samples determines the number of discrete samples taken
- The number of bits used to store the sample determines the number of variations in amplitude
- For instance, a 16 bit sample provides 65,536 possible amplitudes
- In this section we looked at producing sampled audio using the Java Sound API
- To open a sound file, you use code like this:
File file = new File("sound.wav");
AudioInputStream stream =
AudioSystem.getAudioInputStream(file);
- To route the
AudioInputStream to the sound system, you use a Line
- Some of the subinterfaces of
Line that we use are SourceDataLine and Clip
- The code to use a line looks something like:
// Get a Clip from the AudioSystem
clip = AudioSystem.getClip();
// Load the samples from the stream
clip.open(stream);
// Begin playback of the sound clip
clip.start();
- We put all the code to open and play a sound file in an example class
Check Yourself
- What is a sound?
- How does a sound change frequency?
- How does a sound change its loudness?
- How is an analog sound converted to a digital format?
- What code can you use to open a sound stream using the Java Sound API?
- Once you open a sound file, what do you use to send the sound stream to the sound system?
^ top
Exercise 14.3
Take one minute to review the Check Yourself questions. We will review the questions after you are ready.
^ top
14.4: Playing Music
Learner Outcomes
At the end of the lesson the student will be able to:
- Discuss the usual types of music formats
- Describe how MIDI music is played
- Play MIDI music files using the Java Sound API
|
^ top
14.4.1: Music Files
- Music plays an important role in many applications
- There are many formats for music files, which we discuss in this section
CD Audio
- One form of sound is Red Book Audio (standard CD format), which plays from a CD
- This produces quality audio and is easy to implement
- Unfortunately, CD audio takes up a lot of space
- Each minute of music takes up about 10MB
- Unless you plan to distribute your program on CD, this is not a good option
MP3 and Ogg Vorbis
- Another option is compressed music
- MP3 and Ogg Vorbis formats are much smaller that CD audio
- Typically, they take about 1MB per minute of music
- The drawback is that the processor must decode the sound before playing it
- This decoding might be noticeable as slow or jerky animation
- The effects depend on what else is going on in your program and the power of your processor
- If processor time is not an issue, then you can get an MP3 or Ogg Vorbis Java decoder
- Both are available from www.javazoom.net
- MP3 is incredibly popular but has licensing issues: mp3licensing.com
- Ogg Vorbis, on the other hand, is license free and may sound better: xiph.org
- Also, if you use either MP3 or Ogg Vorbis, make sure you do not preload the sound files into main memory
- While compressed sound files are relatively compact, uncompressed files can take up huge amounts of memory
- Thus, you want to stream your music directly from disk
^ top
14.4.2: Introduction to MIDI
- A better solution for many projects is to use MIDI music
- MIDI is an acronym for musical instrument digital interface
- MIDI is not sampled music but is more like a digital sheet music
- A MIDI file gives instructions on which note to play on which instrument
- A synthesizer creates music from the instructions and which is played by the sound system
- Since MIDI files contain instructions instead of sampled music, they are a much smaller size
- Typically, a MIDI file is measured in kilobytes rather than megabytes
- However, because music is synthesized, some instruments may not sound realistic
- The sound quality depends on the soundbanks in the synthesizer
- On the other hand, a creative musician can usually mask the deficiencies of MIDI
- For information on MIDI technology: MIDI Manufacturers Association
Getting MIDI Music
- To play MIDI music, you will need some MIDI files
- You can get free MIDI files from the Internet
- However, just because the MIDI file is available does not mean that it is legal to use it in a commercially
- Music is copyrighted and you need to respect the copyright
- You can write your own MIDI music if you are musically inclined
- Several programs let you record MIDI music on your own computer
- Also, Cabrillo offers courses in MIDI such as MUS 57: Music and Computers
^ top
14.4.3: Processing a MIDI File
- Let us look at how Java processes a MIDI file
- In Java, a
Sequence object holds the instructions from a MIDI file
- Thus a
Sequence object is like a song
- To play a MIDI file, you first load a MIDI file into a
Sequence object
- Then the
Sequence is played by a Sequencer which transmits the instructions to a Synthesizer
- The
Synthesizer contains the soundbanks which produce the sounds played by the audio system
- This process is shown in simplified form in the following diagram from the book, Killer Game Programming in Java

Soundbanks
- The Java Sound API synthesizes MIDI music through a soundbank
- A soundbank is a collection of instrument sounds
- The JDK and JRE includes a soundbank in the directories:
C:\Program Files\Java\<version>\lib\audio
C:\Program Files\Java\<version>\jre\lib\audio
- The exact directories depend upon the version of Java you have installed
- The soundbank file is named:
soundbank.gm
- If Java cannot find a soundbank file, then it uses the hardware MIDI port
- Since the quality of the hardware MIDI is an unknown, you might want to include a soundbank with your application
- Several soundbanks of varying quality are available by following the link:
^ top
14.4.4: Playing MIDI Music
- The Java Sound API provides MIDI in the
javax.sound.midi package
- To play MIDI music, you need a
Sequence and a Sequencer
- You load the MIDI data into a
Sequence using code like:
Sequence song = MidiSystem.getSequence(
getClass().getResource(filename));
- Then you create a Sequencer object:
Sequencer sequencer = MidiSystem.getSequencer();
- To open the sequence and play it, you use:
sequencer.setSequence(song);
sequencer.open();
sequencer.start();
Responding When the Track Ends
- By default, a sequence will play once and then stop
- Usually, you want to loop the music in your application
- To loop a
Sequence, you need to know when the sequence is finished to you can start the Sequencer again
- The Java Sound MIDI API has a
MetaEventListener you can use to listen for end-of-track events
- This listener interface has one method that must be written:
public void meta(MetaMessage event)
- You add the listener object implementing the interface to the
Sequencer by using:
sequencer.addMetaEventListener(listenerObject);
- The
meta() method gets called frequently but we only want to respond to an end-of-track event:
public static final int END_OF_TRACK = 47;
public void meta(MetaMessage event) {
if (event.getType() == END_OF_TRACK) {
// do something about the event
}
}
^ top
14.4.5: The MidiSong Class
- Let us put the code snippets from above into a working program
- The following class, named
MidiSong, lets you load and play a MIDI file
- In addition, it reports some of the available information about the MIDI file
- We can use this class to experiment with different MIDI files
- To give us sounds to play, we can use the following files:
Class MidiSong for Playing MIDI Files
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
import java.io.*;
import javax.sound.midi.*;
public class MidiSong implements MetaEventListener {
// MIDI meta-event constant used to signal the
// end of a track
public static final int END_OF_TRACK = 47;
private Sequencer sequencer;
private Sequence song;
private boolean loop;
// Driver to play midi files
public static void main(String[] args) {
MidiSong midi =
new MidiSong("sounds/bsg.midi");
}
public MidiSong(String filename) {
try {
song = MidiSystem.getSequence(
getClass().getResource(filename));
sequencer = MidiSystem.getSequencer();
sequencer.setSequence(song);
sequencer.open();
sequencer.addMetaEventListener(this);
sequencer.start();
} catch (InvalidMidiDataException e) {
System.out.println("Bad midi file: "
+ filename);
System.exit(1);
} catch (MidiUnavailableException e) {
System.out.println("No sequencer available");
System.exit(1);
} catch (IOException e) {
System.out.println("Could not read: "
+ filename);
System.exit(1);
}
displayMidiInfo(filename);
}
private void displayMidiInfo(String filename) {
System.out.println("Midi File: " + filename);
System.out.println(" Timing resolution: "
+ song.getResolution());
System.out.println(" Number of ticks: "
+ song.getTickLength());
System.out.println(" Number of tracks: "
+ song.getTracks().length);
System.out.println(" Number of patches: "
+ song.getPatchList().length);
long lengthMillis =
song.getMicrosecondLength() / 1000L;
System.out.println(" Duration: "
+ (lengthMillis / 1000.0) + " secs");
}
// Called by the sound system when a meta event occurs
public void meta(MetaMessage event) {
if (event.getType() == END_OF_TRACK) {
sleep(300); // let buffer clear?
close();
System.exit(0);
}
}
// Close the sequencer.
public void close() {
if (sequencer.isOpen()) {
sequencer.close();
}
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch(InterruptedException e) {
System.out.println("Sleep Interrupted");
}
}
}
|
^ top
14.4.6: Summary
- In this section, we discussed the typical music formats
- These formats include:
- Red Book Audio (CD Audio)
- Compressed Audio like MP3 and Ogg Vorbis
- MIDI
- Oftentimes the best choice is MIDI
- The reason is that MIDI files are smaller than sampled music files
- A MIDI file contains instructions on which note to play on which instrument
- A synthesizer then creates music from the instructions and which is played by the sound system
- The Java Sound API provides MIDI in the
javax.sound.midi package
- To play MIDI music, you need a
Sequence and a Sequencer
- You load the MIDI data into a
Sequence using code like:
Sequence song = MidiSystem.getSequence(
getClass().getResource(filename));
- Then you get a
Sequencer object:
Sequencer sequencer = MidiSystem.getSequencer();
- To open the sequence and play it, you use:
sequencer.setSequence(song);
sequencer.open();
sequencer.start();
- To respond to end-of-track events, you need to write a
MetaEventListener
- We put all the code to open and play a MIDI file in the
MidiSong class
Check Yourself
- What is Ogg Vorbis?
- What are the problems with using compressed music files like MP3's?
- What is MIDI?
- How are MIDI sounds produced?
- What are the advantages of producing music using MIDI?
- What are the disadvantages of producing music using MIDI
- How can you improve the quality of MIDI sound files?
- What code do you use to play MIDI files using Java?
- How do you test for the end of a MIDI track?
^ top
Exercise 14.4
Take one minute to review the Check Yourself questions. We will review the questions after you are ready.
^ top
Wrap Up
Due Next: Course Project (5/26/10) Work on your project!
^ top
Home
| Blackboard
| Schedule
| Room Policies
| Syllabus
Help
| FAQ's
| HowTo's
| Links
Last Updated: May 19 2010 @12:28:28
|