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