1 import java.awt.BorderLayout;
2 import java.awt.Color;
3 import java.awt.Graphics;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.awt.event.WindowAdapter;
7 import java.awt.event.WindowEvent;
8 import java.io.ByteArrayInputStream;
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.InputStream;
12 import java.io.IOException;
13 import java.util.Arrays;
14 import javax.swing.JButton;
15 import javax.swing.JComponent;
16 import javax.swing.JFileChooser;
17 import javax.swing.JFrame;
18 import javax.swing.JOptionPane;
19 import javax.swing.JPanel;
20 import javax.sound.sampled.AudioFileFormat;
21 import javax.sound.sampled.AudioSystem;
22 import javax.sound.sampled.Clip;
23
24 /**
25 This program processes a 16 bit uncompressed mono .WAV file.
26 */
27 public class SoundClip
28 {
29 private static final int HEADER_SIZE = 44;
30 private byte[] header = new byte[HEADER_SIZE];
31 private int[] samples;
32 private int sampleRate;
33
34 public int[] getSampleValues()
35 {
36 return samples;
37 }
38
39 public int getSampleRate()
40 {
41 return sampleRate;
42 }
43
44 /**
45 Displays a file chooser for picking a clip.
46 */
47 public void pick()
48 {
49 JFileChooser chooser = new JFileChooser(".");
50 if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
51 {
52 load(chooser.getSelectedFile());
53 }
54 }
55
56 /**
57 Shows a frame with the sample values and a Play button
58 */
59 public void show()
60 {
61 if (samples != null)
62 {
63 JFrame frame = new SoundClipFrame();
64 frame.setVisible(true);
65 }
66 }
67
68 // -----------------------------------------------------------------
69
70 /*
71 The code below processes a file in the WAV format.
72 You can use this program to manipulate sound files without
73 reading or understanding the code below.
74
75 The code uses Java features that are introduced in later chapters
76 as well as the internals of the WAV format
77 (https://ccrma.stanford.edu/courses/422/projects/WaveFormat/)
78 */
79
80 /**
81 Loads a picture from a given source.
82 @param source the image source.
83 @return true if the file was loaded successfully
84 */
85 private void load(File source)
86 {
87 try
88 {
89 InputStream in = new FileInputStream(source);
90 for (int i = 0; i < HEADER_SIZE; i++)
91 {
92 int b = in.read();
93 if (b == -1)
94 {
95 error("Premature end of file");
96 }
97 header[i] = (byte) b;
98 }
99
100 int formatType = getUnsignedInt2(20);
101 if (formatType != 1)
102 {
103 error("Not an uncompressed sound file.");
104 }
105 int numChannels = getUnsignedInt2(22);
106 if (numChannels != 1)
107 {
108 error("Not a mono sound file.");
109 }
110
111 sampleRate = getUnsignedInt2(24);
112
113 int bitsPerSample = getUnsignedInt2(34);
114 if (bitsPerSample != 16)
115 {
116 error("Not a 16 bit sound file.");
117 }
118
119 // Read data size and allocate data array
120 int dataSize = getUnsignedInt4(40) / 2; // 2 bytes per data
121 samples = new int[dataSize];
122
123 // Read sound data
124 for (int i = 0; i < dataSize; i++)
125 {
126 samples[i] = getSignedInt2(in);
127 }
128 }
129 catch (Exception ex)
130 {
131 error(ex.getMessage());
132 }
133 }
134
135 private void error(String message)
136 {
137 JOptionPane.showMessageDialog(null, message);
138 }
139
140 /**
141 Gets a byte from the header as an unsigned value.
142 @param offset the offset from the start of the header
143 @return the integer
144 */
145 private int getUnsignedInt1(int offset)
146 {
147 int result = header[offset];
148 if (result >= 0) { return result; }
149 else { return result + 256; }
150 }
151
152 /**
153 Gets an unsigned 4-byte integer from the header
154 @param offset the offset from the start of the header
155 @return the integer
156 */
157 private int getUnsignedInt4(int offset)
158 {
159 int result = 0;
160 int base = 1;
161 for (int i = 0; i < 4; i++)
162 {
163 result = result + getUnsignedInt1(offset + i) * base;
164 base = base * 256;
165 }
166 return result;
167 }
168
169 /**
170 Gets an unsigned 2-byte integer from a random access file.
171 @param in the file
172 @return the integer
173 */
174 private int getUnsignedInt2(int offset)
175 {
176 return getUnsignedInt1(offset) + 256 * getUnsignedInt1(offset + 1);
177 }
178
179 /**
180 Gets a signed 2-byte integer from a random access file.
181 @param in the file
182 @return the integer
183 */
184 private static int getSignedInt2(InputStream in)
185 throws IOException
186 {
187 int lo = in.read();
188 int hi = in.read();
189 int result = lo + 256 * hi;
190 if (result >= 32768) { result = result - 65536; }
191 return result;
192 }
193
194 private static int frames;
195 private static int offsets;
196
197 class SoundClipFrame extends JFrame
198 {
199 private int[] samples;
200
201
202 public SoundClipFrame()
203 {
204 frames++;
205 offsets++;
206 samples = Arrays.copyOf(SoundClip.this.samples,
207 SoundClip.this.samples.length);
208
209 addWindowListener(new WindowAdapter()
210 {
211 @Override public void windowClosing(WindowEvent event)
212 {
213 frames--;
214 if (frames == 0) System.exit(0);
215 }
216 });
217
218 final int FRAME_WIDTH = 800;
219 final int FRAME_HEIGHT = 200;
220 final int OFFSET_WIDTH = 100;
221
222 setBounds(offsets * OFFSET_WIDTH, offsets * OFFSET_WIDTH, FRAME_WIDTH, FRAME_HEIGHT);
223
224 JComponent component = new JComponent()
225 {
226 public void paintComponent(Graphics graph)
227 {
228 int increment = samples.length / getWidth();
229
230 final int LARGEST = 32767;
231 int x = 0;
232 for (int i = 0; i < samples.length; i = i + increment)
233 {
234 int value = samples[i];
235 value = Math.min(LARGEST, value);
236 value = Math.max(-LARGEST, value);
237
238 int height = getHeight() / 2;
239 int y = height - samples[i] * height / LARGEST;
240 graph.drawLine(x, y, x, height);
241 x++;
242 }
243 }
244 };
245
246 add(component);
247 JPanel panel = new JPanel();
248 JButton button = new JButton("Play");
249 button.addActionListener(new ActionListener()
250 {
251 public void actionPerformed(ActionEvent event) { play(); }
252 });
253 panel.add(button);
254 add(panel, BorderLayout.SOUTH);
255 }
256
257 private void play()
258 {
259 byte[] out = new byte[HEADER_SIZE + 2 * samples.length];
260 for (int i = 0; i < HEADER_SIZE; i++)
261 {
262 out[i] = header[i];
263 }
264 for (int i = 0; i < samples.length; i++)
265 {
266 int value = samples[i];
267 if (value < 0) { value = value + 65536; }
268 out[HEADER_SIZE + 2 * i] = (byte)(value % 256);
269 out[HEADER_SIZE + 2 * i + 1] = (byte)(value / 256);
270 }
271
272 try
273 {
274 Clip clip = AudioSystem.getClip();
275 clip.open(AudioSystem.getAudioInputStream(new ByteArrayInputStream(out)));
276 clip.start();
277 }
278 catch (Exception ex)
279 {
280 error(ex.getMessage());
281 }
282 }
283 }
284 }
285