What We Will Cover
Illuminations
- You should have finished your project design by now
- By this week you should have completed the first implementation
What to take next?
- Schedule of Classes
- After completing CS-20J you are qualified to take:
- CS-19: C++ programming and software design methodologies
- CS-21: Introduction to Data Structures and Algorithms (Spring 2010 and needs Math 5A)
- CS-23: Discrete Mathematics (Spring 2010 and needs Math 5A)
- CS-24: Elementary Computer Organization (cancelled for Fall 2009 due to budget cutbacks)
- CS-19 has an initial review section such that you could learn the syntax with a little effort
- Other programming courses for applying your skills in other environments:
- CIS 131: Perl Programming in a Unix Environment (Summer)
- CIS 132: Introduction to Internet Programming (classroom and online sections available)
^ 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 now shipping
- May cause a dramatic shift in the programming paradigm
- 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
- These classes and interfaces are shown in the following table:
| 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 threading. |
- You can see the inheritance relationship of these modules in the following diagram:

- 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() |
Creates a new Thread object with default settings. |
| Thread(Runnable) |
Creates a new Thread object from any object that implements the Runnable interface |
| Thread(String) |
Creates a new Thread object with the specified name. |
| Thread(Runnable, String) |
Creates 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
- To do this, we create a boolean instance variable named
stop
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++)
/li>
- 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 three different approaches to sound:
Applet play() method
AudioClip class
- Java Sound API
- The most flexible and complex of these 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
- 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
Simplified Code for Java 5+
^ top
14.3.5: Playing a Sound
- Let us put the code snippets we saw above into a working program
- The following class, named
SoundClip, lets you load a sound file into a clip
- In addition, it reports some of the available information about the sound file
- We can use this class to experiment with different sound file
- The class has provisions for playing a sound once or repeating it many times
- To give us a sound to play, we can use the following file:
Draining a Sound
- Note the calls to
clip.drain() in the following code
- Sounds are played in a separate thread
- The type of thread used is known as a daemon thread
- A daemon thread is a thread that will exit when all regular threads are finished
- Thus, daemon threads do not keep the program from ending like regular threads
- Since the only regular thread running is the main thread, the program will end when
main() completes
- Which is just after starting the sound
- To let us hear the sound, we need to "block" the main thread until the sound is finished playing
- We block using a call to
clip.drain()
- However,
clip.drain() did not work reliably on all the computers I tested it on
- If you have difficult with
clip.drain() you can use the drain() method instead
Class SoundTest for Testing Sound 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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import java.io.*;
import javax.sound.sampled.*;
import java.text.DecimalFormat;
public class SoundTest implements LineListener {
private AudioFormat format;
private AudioInputStream stream;
private Clip clip;
// Driver to play sound files
public static void main(String[] args) {
SoundTest sound =
new SoundTest("sounds/voice.wav");
sound.play();
//sound.loop(1);
//sound.loop(Clip.LOOP_CONTINUOUSLY);
}
public SoundTest(String filename) {
try {
stream = AudioSystem.getAudioInputStream(
getClass().getResource(filename));
format = stream.getFormat();
clip = AudioSystem.getClip();
clip.addLineListener(this);
clip.open(stream);
} catch (IOException ex) {
ex.printStackTrace();
} catch (UnsupportedAudioFileException ex) {
ex.printStackTrace();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
}
displaySoundInfo(filename);
}
// Play the sound clip
public void play() {
clip.start(); // start playback
clip.drain(); // block until done
//drain(0);
}
// Play the sound repeatedly
public void loop(int numRepeats) {
clip.setFramePosition(0); // allows replay pre JDK 6
clip.loop(numRepeats); // plays numRepeats + 1
long time = clip.getMicrosecondLength() / 1000L;
clip.drain(); // block until done
//drain(numRepeats);
}
// Display sound file information
private void displaySoundInfo(String filename) {
System.out.println("Sample file: " + filename);
System.out.println(" " + format);
System.out.println(" Encoded format: "
+ format.getEncoding());
System.out.println(" Sampling rate: "
+ (int) format.getSampleRate());
System.out.println(" Sample size: "
+ format.getSampleSizeInBits() + "-bit");
System.out.println(" Sample channels: "
+ format.getChannels());
System.out.println(" Frame size: "
+ format.getFrameSize() + " bytes/frame");
int length = (int) (stream.getFrameLength()
* format.getFrameSize());
System.out.println(" Sample size: "
+ length + " bytes");
System.out.println(" Buffer size: "
+ clip.getBufferSize() + " bytes");
long lengthMillis =
clip.getMicrosecondLength() / 1000L;
System.out.println(" Duration: "
+ (lengthMillis / 1000.0) + " secs");
}
//
public void update(LineEvent event) {
System.out.println("LineEvent: " + event);
if (event.getType() == LineEvent.Type.STOP) {
Line line = event.getLine();
line.close();
}
}
// Use if clip.drain() proves unreliable
private void drain(int numRepeats) {
long millis = clip.getMicrosecondLength() / 1000L;
if (numRepeats == Clip.LOOP_CONTINUOUSLY) {
numRepeats = 10; // limit for test purposes
}
for (int i = 0; i < numRepeats + 1; i++) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
}
|
^ 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);
AudioFormat format = stream.getFormat();
- 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 the
SoundClip 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/27/09) Work on your project!
^ top
Home
| Blackboard
| Announcements
| Schedule
| Room Policies
| Course Info
Help
| FAQ's
| HowTo's
| Links
Last Updated: May 20 2009 @15:21:24
|