/Users/lyon/j4p/src/sound/soundDemo/Juke.java

1    package sound.soundDemo; 
2     
3    /* 
4     * Copyright 2002 Sun Microsystems, Inc. All rights reserved. 
5     * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 
6     */ 
7     
8     
9    import javax.sound.midi.*; 
10   import javax.sound.sampled.*; 
11   import javax.swing.*; 
12   import javax.swing.border.*; 
13   import javax.swing.event.ChangeEvent; 
14   import javax.swing.event.ChangeListener; 
15   import javax.swing.event.TableModelEvent; 
16   import javax.swing.table.AbstractTableModel; 
17   import javax.swing.table.TableColumn; 
18   import javax.swing.table.TableModel; 
19   import java.awt.*; 
20   import java.awt.event.ActionEvent; 
21   import java.awt.event.ActionListener; 
22   import java.awt.event.WindowAdapter; 
23   import java.awt.event.WindowEvent; 
24   import java.awt.font.FontRenderContext; 
25   import java.awt.font.LineBreakMeasurer; 
26   import java.awt.font.TextAttribute; 
27   import java.awt.font.TextLayout; 
28   import java.awt.geom.Arc2D; 
29   import java.awt.geom.Ellipse2D; 
30   import java.awt.geom.Rectangle2D; 
31   import java.io.BufferedInputStream; 
32   import java.io.File; 
33   import java.io.FileInputStream; 
34   import java.net.URL; 
35   import java.text.AttributedCharacterIterator; 
36   import java.text.AttributedString; 
37   import java.util.Vector; 
38    
39    
40   /** 
41    * A JukeBox for sampled and midi sound files.  Features duration progress, 
42    * seek slider, pan and volume controls. 
43    * 
44    * @version @(#)Juke.java   1.21 02/02/06 
45    * @author Brian Lichtenwalter 
46    */ 
47   public class Juke extends JPanel implements Runnable, LineListener, MetaEventListener, ControlContext { 
48    
49       final int bufSize = 16384; 
50       PlaybackMonitor playbackMonitor = new PlaybackMonitor(); 
51    
52       Vector sounds = new Vector(); 
53       Thread thread; 
54       Sequencer sequencer; 
55       boolean midiEOM, audioEOM; 
56       Synthesizer synthesizer; 
57       MidiChannel channels[]; 
58       Object currentSound; 
59       String currentName; 
60       double duration; 
61       int num; 
62       boolean bump; 
63       boolean paused = false; 
64       JButton startB, pauseB, loopB, prevB, nextB; 
65       JTable table; 
66       JSlider panSlider, gainSlider; 
67       JSlider seekSlider; 
68       JukeTable jukeTable; 
69       Loading loading; 
70       Credits credits; 
71       String errStr; 
72       JukeControls controls; 
73    
74    
75       public Juke(String dirName) { 
76           setLayout(new BorderLayout()); 
77           setBorder(new EmptyBorder(5, 5, 5, 5)); 
78    
79           if (dirName != null) { 
80               loadJuke(dirName); 
81           } 
82    
83           JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 
84                   jukeTable = new JukeTable(), controls = new JukeControls()); 
85           splitPane.setContinuousLayout(true); 
86           add(splitPane); 
87       } 
88    
89    
90       public void open() { 
91    
92           try { 
93    
94               sequencer = MidiSystem.getSequencer(); 
95    
96               if (sequencer instanceof Synthesizer) { 
97                   synthesizer = (Synthesizer) sequencer; 
98                   channels = synthesizer.getChannels(); 
99               } 
100   
101          } catch (Exception ex) { 
102              ex.printStackTrace(); 
103              return; 
104          } 
105          sequencer.addMetaEventListener(this); 
106          (credits = new Credits()).start(); 
107      } 
108   
109   
110      public void close() { 
111          if (credits != null && credits.isAlive()) { 
112              credits.interrupt(); 
113          } 
114          if (thread != null && startB != null) { 
115              startB.doClick(0); 
116          } 
117          if (jukeTable != null && jukeTable.frame != null) { 
118              jukeTable.frame.dispose(); 
119              jukeTable.frame = null; 
120          } 
121          if (sequencer != null) { 
122              sequencer.close(); 
123          } 
124      } 
125   
126   
127      public void loadJuke(String name) { 
128          try { 
129              File file = new File(name); 
130              if (file != null && file.isDirectory()) { 
131                  String files[] = file.list(); 
132                  for (int i = 0; i < files.length; i++) { 
133                      File leafFile = new File(file.getAbsolutePath(), files[i]); 
134                      if (leafFile.isDirectory()) { 
135                          loadJuke(leafFile.getAbsolutePath()); 
136                      } else { 
137                          addSound(leafFile); 
138                      } 
139                  } 
140              } else if (file != null && file.exists()) { 
141                  addSound(file); 
142              } 
143          } catch (SecurityException ex) { 
144              reportStatus(ex.toString()); 
145              JavaSound.showInfoDialog(); 
146          } catch (Exception ex) { 
147              reportStatus(ex.toString()); 
148          } 
149      } 
150   
151   
152      private void addSound(File file) { 
153          String s = file.getName().toLowerCase(); 
154          if (s.endsWith(".au") || s.endsWith(".rmf") || 
155                  s.endsWith(".mid") || s.endsWith(".wav") || 
156                  s.endsWith(".aif") || s.endsWith(".aiff")) { 
157              sounds.add(file); 
158          } 
159      } 
160   
161   
162      public boolean loadSound(Object object) { 
163   
164          duration = 0.0; 
165          (loading = new Loading()).start(); 
166   
167          if (object instanceof URL) { 
168              currentName = ((URL) object).getFile(); 
169              playbackMonitor.repaint(); 
170              try { 
171                  currentSound = AudioSystem.getAudioInputStream((URL) object); 
172              } catch (Exception e) { 
173                  try { 
174                      currentSound = MidiSystem.getSequence((URL) object); 
175                  } catch (InvalidMidiDataException imde) { 
176                      System.out.println("Unsupported audio file."); 
177                      return false; 
178                  } catch (Exception ex) { 
179                      ex.printStackTrace(); 
180                      currentSound = null; 
181                      return false; 
182                  } 
183              } 
184          } else if (object instanceof File) { 
185              currentName = ((File) object).getName(); 
186              playbackMonitor.repaint(); 
187              try { 
188                  currentSound = AudioSystem.getAudioInputStream((File) object); 
189              } catch (Exception e1) { 
190                  // load midi & rmf as inputstreams for now 
191                  //try { 
192                  //currentSound = MidiSystem.getSequence((File) object); 
193                  //} catch (Exception e2) { 
194                  try { 
195                      FileInputStream is = new FileInputStream((File) object); 
196                      currentSound = new BufferedInputStream(is, 1024); 
197                  } catch (Exception e3) { 
198                      e3.printStackTrace(); 
199                      currentSound = null; 
200                      return false; 
201                  } 
202                  //} 
203              } 
204          } 
205   
206   
207          loading.interrupt(); 
208   
209          // user pressed stop or changed tabs while loading 
210          if (sequencer == null) { 
211              currentSound = null; 
212              return false; 
213          } 
214   
215          if (currentSound instanceof AudioInputStream) { 
216              try { 
217                  AudioInputStream stream = (AudioInputStream) currentSound; 
218                  AudioFormat format = stream.getFormat(); 
219   
220                  /** 
221                   * we can't yet open the device for ALAW/ULAW playback, 
222                   * convert ALAW/ULAW to PCM 
223                   */ 
224                  if ((format.getEncoding() == AudioFormat.Encoding.ULAW) || 
225                          (format.getEncoding() == AudioFormat.Encoding.ALAW)) { 
226                      AudioFormat tmp = new AudioFormat( 
227                              AudioFormat.Encoding.PCM_SIGNED, 
228                              format.getSampleRate(), 
229                              format.getSampleSizeInBits() * 2, 
230                              format.getChannels(), 
231                              format.getFrameSize() * 2, 
232                              format.getFrameRate(), 
233                              true); 
234                      stream = AudioSystem.getAudioInputStream(tmp, stream); 
235                      format = tmp; 
236                  } 
237                  DataLine.Info info = new DataLine.Info( 
238                          Clip.class, 
239                          stream.getFormat(), 
240                          ((int) stream.getFrameLength() * 
241                          format.getFrameSize())); 
242   
243                  Clip clip = (Clip) AudioSystem.getLine(info); 
244                  clip.addLineListener(this); 
245                  clip.open(stream); 
246                  currentSound = clip; 
247                  seekSlider.setMaximum((int) stream.getFrameLength()); 
248              } catch (Exception ex) { 
249                  ex.printStackTrace(); 
250                  currentSound = null; 
251                  return false; 
252              } 
253          } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) { 
254              try { 
255                  sequencer.open(); 
256                  if (currentSound instanceof Sequence) { 
257                      sequencer.setSequence((Sequence) currentSound); 
258                  } else { 
259                      sequencer.setSequence((BufferedInputStream) currentSound); 
260                  } 
261                  seekSlider.setMaximum((int) (sequencer.getMicrosecondLength() / 1000)); 
262   
263              } catch (InvalidMidiDataException imde) { 
264                  System.out.println("Unsupported audio file."); 
265                  currentSound = null; 
266                  return false; 
267              } catch (Exception ex) { 
268                  ex.printStackTrace(); 
269                  currentSound = null; 
270                  return false; 
271              } 
272          } 
273   
274          seekSlider.setValue(0); 
275   
276          // enable seek, pan, and gain sliders for sequences as well as clips 
277          seekSlider.setEnabled(true); 
278          panSlider.setEnabled(true); 
279          gainSlider.setEnabled(true); 
280   
281          duration = getDuration(); 
282   
283          return true; 
284      } 
285   
286   
287      public void playSound() { 
288          playbackMonitor.start(); 
289          setGain(); 
290          setPan(); 
291          midiEOM = audioEOM = bump = false; 
292          if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream && thread != null) { 
293              sequencer.start(); 
294              while (!midiEOM && thread != null && !bump) { 
295                  try { 
296                      Thread.sleep(99); 
297                  } catch (Exception e) { 
298                      break; 
299                  } 
300              } 
301              sequencer.stop(); 
302              sequencer.close(); 
303          } else if (currentSound instanceof Clip && thread != null) { 
304              Clip clip = (Clip) currentSound; 
305              clip.start(); 
306              try { 
307                  Thread.sleep(99); 
308              } catch (Exception e) { 
309              } 
310              while ((paused || clip.isActive()) && thread != null && !bump) { 
311                  try { 
312                      Thread.sleep(99); 
313                  } catch (Exception e) { 
314                      break; 
315                  } 
316              } 
317              clip.stop(); 
318              clip.close(); 
319          } 
320          currentSound = null; 
321          playbackMonitor.stop(); 
322      } 
323   
324   
325      public double getDuration() { 
326          double duration = 0.0; 
327          if (currentSound instanceof Sequence) { 
328              duration = ((Sequence) currentSound).getMicrosecondLength() / 1000000.0; 
329          } else if (currentSound instanceof BufferedInputStream) { 
330              duration = sequencer.getMicrosecondLength() / 1000000.0; 
331          } else if (currentSound instanceof Clip) { 
332              Clip clip = (Clip) currentSound; 
333              duration = clip.getBufferSize() / 
334                      (clip.getFormat().getFrameSize() * clip.getFormat().getFrameRate()); 
335          } 
336          return duration; 
337      } 
338   
339   
340      public double getSeconds() { 
341          double seconds = 0.0; 
342          if (currentSound instanceof Clip) { 
343              Clip clip = (Clip) currentSound; 
344              seconds = clip.getFramePosition() / clip.getFormat().getFrameRate(); 
345          } else if ((currentSound instanceof Sequence) || (currentSound instanceof BufferedInputStream)) { 
346              try { 
347                  seconds = sequencer.getMicrosecondPosition() / 1000000.0; 
348              } catch (IllegalStateException e) { 
349                  System.out.println("TEMP: IllegalStateException " + 
350                          "on sequencer.getMicrosecondPosition(): " + e); 
351              } 
352          } 
353          return seconds; 
354      } 
355   
356   
357      public void update(LineEvent event) { 
358          if (event.getType() == LineEvent.Type.STOP && !paused) { 
359              audioEOM = true; 
360          } 
361      } 
362   
363   
364      public void meta(MetaMessage message) { 
365          if (message.getType() == 47) {  // 47 is end of track 
366              midiEOM = true; 
367          } 
368      } 
369   
370   
371      private void reportStatus(String msg) { 
372          if ((errStr = msg) != null) { 
373              System.out.println(errStr); 
374              playbackMonitor.repaint(); 
375          } 
376          if (credits != null && credits.isAlive()) { 
377              credits.interrupt(); 
378          } 
379      } 
380   
381   
382      public Thread getThread() { 
383          return thread; 
384      } 
385   
386   
387      public void start() { 
388          thread = new Thread(this); 
389          thread.setName("Juke"); 
390          thread.start(); 
391      } 
392   
393   
394      public void stop() { 
395          if (thread != null) { 
396              thread.interrupt(); 
397          } 
398          thread = null; 
399      } 
400   
401   
402      public void run() { 
403          do { 
404              table.scrollRectToVisible(new Rectangle(0, 0, 1, 1)); 
405              for (; num < sounds.size() && thread != null; num++) { 
406                  table.scrollRectToVisible(new Rectangle(0, (num + 2) * (table.getRowHeight() + table.getRowMargin()), 1, 1)); 
407                  table.setRowSelectionInterval(num, num); 
408                  if (loadSound(sounds.get(num)) == true) { 
409                      playSound(); 
410                  } 
411                  // take a little break between sounds 
412                  try { 
413                      Thread.sleep(222); 
414                  } catch (Exception e) { 
415                      break; 
416                  } 
417              } 
418              num = 0; 
419          } while (loopB.isSelected() && thread != null); 
420   
421          if (thread != null) { 
422              startB.doClick(); 
423          } 
424          thread = null; 
425          currentName = null; 
426          currentSound = null; 
427          playbackMonitor.repaint(); 
428      } 
429   
430   
431      public void setPan() { 
432   
433          int value = panSlider.getValue(); 
434   
435          if (currentSound instanceof Clip) { 
436              try { 
437                  Clip clip = (Clip) currentSound; 
438                  FloatControl panControl = 
439                          (FloatControl) clip.getControl(FloatControl.Type.PAN); 
440                  panControl.setValue(value / 100.0f); 
441              } catch (Exception ex) { 
442                  ex.printStackTrace(); 
443              } 
444          } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) { 
445              for (int i = 0; i < channels.length; i++) { 
446                  channels[i].controlChange(10, (int) (((double) value + 100.0) / 200.0 * 127.0)); 
447              } 
448          } 
449      } 
450   
451   
452      public void setGain() { 
453          double value = gainSlider.getValue() / 100.0; 
454   
455          if (currentSound instanceof Clip) { 
456              try { 
457                  Clip clip = (Clip) currentSound; 
458                  FloatControl gainControl = 
459                          (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); 
460                  float dB = (float) 
461                          (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0); 
462                  gainControl.setValue(dB); 
463              } catch (Exception ex) { 
464                  ex.printStackTrace(); 
465              } 
466          } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) { 
467              for (int i = 0; i < channels.length; i++) { 
468                  channels[i].controlChange(7, (int) (value * 127.0)); 
469   
470              } 
471          } 
472      } 
473   
474   
475      /** 
476       * GUI controls for start, stop, previous, next, pan and gain. 
477       */ 
478      class JukeControls extends JPanel implements ActionListener, ChangeListener { 
479   
480          public JukeControls() { 
481              setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 
482   
483              JPanel p1 = new JPanel(); 
484              p1.setLayout(new BoxLayout(p1, BoxLayout.Y_AXIS)); 
485              p1.setBorder(new EmptyBorder(10, 0, 5, 0)); 
486              JPanel p2 = new JPanel(); 
487              startB = addButton("Start", p2, sounds.size() != 0); 
488              pauseB = addButton("Pause", p2, false); 
489              p1.add(p2); 
490              JPanel p3 = new JPanel(); 
491              prevB = addButton("<<", p3, false); 
492              nextB = addButton(">>", p3, false); 
493              p1.add(p3); 
494              add(p1); 
495   
496              JPanel p4 = new JPanel(new BorderLayout()); 
497              EmptyBorder eb = new EmptyBorder(5, 20, 10, 20); 
498              BevelBorder bb = new BevelBorder(BevelBorder.LOWERED); 
499              p4.setBorder(new CompoundBorder(eb, bb)); 
500              p4.add(playbackMonitor); 
501              seekSlider = new JSlider(JSlider.HORIZONTAL, 0, 100, 0); 
502              seekSlider.setEnabled(false); 
503              seekSlider.addChangeListener(this); 
504              p4.add("South", seekSlider); 
505              add(p4); 
506   
507              JPanel p5 = new JPanel(); 
508              p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS)); 
509              p5.setBorder(new EmptyBorder(5, 5, 10, 5)); 
510              panSlider = new JSlider(-100, 100, 0); 
511              panSlider.addChangeListener(this); 
512              TitledBorder tb = new TitledBorder(new EtchedBorder()); 
513              tb.setTitle("Pan = 0.0"); 
514              panSlider.setBorder(tb); 
515              p5.add(panSlider); 
516              gainSlider = new JSlider(0, 100, 80); 
517              gainSlider.addChangeListener(this); 
518              tb = new TitledBorder(new EtchedBorder()); 
519              tb.setTitle("Gain = 80"); 
520              gainSlider.setBorder(tb); 
521              p5.add(gainSlider); 
522              add(p5); 
523          } 
524   
525          private JButton addButton(String name, JPanel panel, boolean state) { 
526              JButton b = new JButton(name); 
527              b.addActionListener(this); 
528              b.setEnabled(state); 
529              panel.add(b); 
530              return b; 
531          } 
532   
533          public void stateChanged(ChangeEvent e) { 
534              JSlider slider = (JSlider) e.getSource(); 
535              int value = slider.getValue(); 
536              if (slider.equals(seekSlider)) { 
537                  if (currentSound instanceof Clip) { 
538                      ((Clip) currentSound).setFramePosition(value); 
539                  } else if (currentSound instanceof Sequence) { 
540                      long dur = ((Sequence) currentSound).getMicrosecondLength(); 
541                      sequencer.setMicrosecondPosition(value * 1000); 
542                  } else if (currentSound instanceof BufferedInputStream) { 
543                      long dur = sequencer.getMicrosecondLength(); 
544                      sequencer.setMicrosecondPosition(value * 1000); 
545                  } 
546                  playbackMonitor.repaint(); 
547                  return; 
548              } 
549              TitledBorder tb = (TitledBorder) slider.getBorder(); 
550              String s = tb.getTitle(); 
551              if (s.startsWith("Pan")) { 
552                  s = s.substring(0, s.indexOf('=') + 1) + 
553                          String.valueOf(value / 100.0); 
554                  if (currentSound != null) { 
555                      setPan(); 
556                  } 
557              } else if (s.startsWith("Gain")) { 
558                  s = s.substring(0, s.indexOf('=') + 1) + 
559                          String.valueOf(value); 
560                  if (currentSound != null) { 
561                      setGain(); 
562                  } 
563              } 
564              tb.setTitle(s); 
565              slider.repaint(); 
566          } 
567   
568   
569          public void setComponentsEnabled(boolean state) { 
570              seekSlider.setEnabled(state); 
571              pauseB.setEnabled(state); 
572              prevB.setEnabled(state); 
573              nextB.setEnabled(state); 
574          } 
575   
576   
577          public void actionPerformed(ActionEvent e) { 
578              JButton button = (JButton) e.getSource(); 
579              if (button.getText().equals("Start")) { 
580                  if (credits != null) { 
581                      credits.interrupt(); 
582                  } 
583                  paused = false; 
584                  num = table.getSelectedRow(); 
585                  num = num == -1 ? 0 : num; 
586                  start(); 
587                  button.setText("Stop"); 
588                  setComponentsEnabled(true); 
589              } else if (button.getText().equals("Stop")) { 
590                  credits = new Credits(); 
591                  credits.start(); 
592                  paused = false; 
593                  stop(); 
594                  button.setText("Start"); 
595                  pauseB.setText("Pause"); 
596                  setComponentsEnabled(false); 
597              } else if (button.getText().equals("Pause")) { 
598                  paused = true; 
599                  if (currentSound instanceof Clip) { 
600                      ((Clip) currentSound).stop(); 
601                  } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) { 
602                      sequencer.stop(); 
603                  } 
604                  playbackMonitor.stop(); 
605                  pauseB.setText("Resume"); 
606              } else if (button.getText().equals("Resume")) { 
607                  paused = false; 
608                  if (currentSound instanceof Clip) { 
609                      ((Clip) currentSound).start(); 
610                  } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) { 
611                      sequencer.start(); 
612                  } 
613                  playbackMonitor.start(); 
614                  pauseB.setText("Pause"); 
615              } else if (button.getText().equals("<<")) { 
616                  paused = false; 
617                  pauseB.setText("Pause"); 
618                  num = num - 1 < 0 ? sounds.size() - 1 : num - 2; 
619                  bump = true; 
620              } else if (button.getText().equals(">>")) { 
621                  paused = false; 
622                  pauseB.setText("Pause"); 
623                  num = num + 1 == sounds.size() ? -1 : num; 
624                  bump = true; 
625              } 
626          } 
627      }  // End JukeControls 
628   
629   
630      /** 
631       * Displays current sound and time elapsed. 
632       */ 
633      public class PlaybackMonitor extends JPanel implements Runnable { 
634   
635          String welcomeStr = "Welcome to Java Sound"; 
636          Thread pbThread; 
637          Color black = new Color(20, 20, 20); 
638          Color jfcBlue = new Color(204, 204, 255); 
639          Color jfcDarkBlue = jfcBlue.darker(); 
640          Font font24 = new Font("serif", Font.BOLD, 24); 
641          Font font28 = new Font("serif", Font.BOLD, 28); 
642          Font font42 = new Font("serif", Font.BOLD, 42); 
643          FontMetrics fm28, fm42; 
644   
645          public PlaybackMonitor() { 
646              fm28 = getFontMetrics(font28); 
647              fm42 = getFontMetrics(font42); 
648          } 
649   
650          public void paint(Graphics g) { 
651              Graphics2D g2 = (Graphics2D) g; 
652              Dimension d = getSize(); 
653              g2.setBackground(black); 
654              g2.clearRect(0, 0, d.width, d.height); 
655              g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 
656              g2.setColor(jfcBlue); 
657   
658              if (errStr != null) { 
659                  g2.setFont(new Font("serif", Font.BOLD, 18)); 
660                  g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); 
661                  g2.drawString("ERROR", 5, 20); 
662                  AttributedString as = new AttributedString(errStr); 
663                  Font font12 = new Font("serif", Font.PLAIN, 12); 
664                  as.addAttribute(TextAttribute.FONT, font12, 0, errStr.length()); 
665                  AttributedCharacterIterator aci = as.getIterator(); 
666                  FontRenderContext frc = g2.getFontRenderContext(); 
667                  LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc); 
668                  float x = 5, y = 25; 
669                  lbm.setPosition(0); 
670                  while (lbm.getPosition() < errStr.length()) { 
671                      TextLayout tl = lbm.nextLayout(d.width - x - 5); 
672                      if (!tl.isLeftToRight()) { 
673                          x = d.width - tl.getAdvance(); 
674                      } 
675                      tl.draw(g2, x, y += tl.getAscent()); 
676                      y += tl.getDescent() + tl.getLeading(); 
677                  } 
678              } else if (currentName == null) { 
679                  FontRenderContext frc = g2.getFontRenderContext(); 
680                  TextLayout tl = new TextLayout(welcomeStr, font28, frc); 
681                  float x = (float) (d.width / 2 - tl.getBounds().getWidth() / 2); 
682                  tl.draw(g2, x, d.height / 2); 
683                  if (credits != null) { 
684                      credits.render(d, g2); 
685                  } 
686              } else { 
687                  g2.setFont(font24); 
688                  g2.drawString(currentName, 5, fm28.getHeight() - 5); 
689                  if (duration <= 0.0) { 
690                      loading.render(d, g2); 
691                  } else { 
692                      double seconds = getSeconds(); 
693                      if (midiEOM || audioEOM) { 
694                          seconds = duration; 
695                      } 
696                      if (seconds > 0.0) { 
697                          g2.setFont(font42); 
698                          String s = String.valueOf(seconds); 
699                          s = s.substring(0, s.indexOf('.') + 2); 
700                          int strW = (int) fm42.getStringBounds(s, g2).getWidth(); 
701                          g2.drawString(s, d.width - strW - 9, fm42.getAscent()); 
702   
703                          int num = 30; 
704                          int progress = (int) (seconds / duration * num); 
705                          double ww = ((double) (d.width - 10) / (double) num); 
706                          double hh = (int) (d.height * 0.25); 
707                          double x = 0.0; 
708                          for (; x < progress; x += 1.0) { 
709                              g2.fill(new Rectangle2D.Double(x * ww + 5, d.height - hh - 5, ww - 1, hh)); 
710                          } 
711                          g2.setColor(jfcDarkBlue); 
712                          for (; x < num; x += 1.0) { 
713                              g2.fill(new Rectangle2D.Double(x * ww + 5, d.height - hh - 5, ww - 1, hh)); 
714                          } 
715                      } 
716                  } 
717              } 
718          } 
719   
720          public void start() { 
721              pbThread = new Thread(this); 
722              pbThread.setName("PlaybackMonitor"); 
723              pbThread.start(); 
724          } 
725   
726          public void stop() { 
727              if (pbThread != null) { 
728                  pbThread.interrupt(); 
729              } 
730              pbThread = null; 
731          } 
732   
733          public void run() { 
734              while (pbThread != null) { 
735                  try { 
736                      Thread.sleep(99); 
737                  } catch (Exception e) { 
738                      break; 
739                  } 
740                  repaint(); 
741              } 
742              pbThread = null; 
743          } 
744      } // End PlaybackMonitor 
745   
746   
747      /** 
748       * Table to display the name of the sound. 
749       */ 
750      class JukeTable extends JPanel implements ActionListener { 
751   
752          TableModel dataModel; 
753          JFrame frame; 
754          JTextField textField; 
755          JButton applyB; 
756   
757          public JukeTable() { 
758              setLayout(new BorderLayout()); 
759              setPreferredSize(new Dimension(260, 300)); 
760   
761              final String[] names = {"#", "Name"}; 
762   
763              dataModel = new AbstractTableModel() { 
764                  public int getColumnCount() { 
765                      return names.length; 
766                  } 
767   
768                  public int getRowCount() { 
769                      return sounds.size(); 
770                  } 
771   
772                  public Object getValueAt(int row, int col) { 
773                      if (col == 0) { 
774                          return new Integer(row); 
775                      } else if (col == 1) { 
776                          Object object = sounds.get(row); 
777                          if (object instanceof File) { 
778                              return ((File) object).getName(); 
779                          } else if (object instanceof URL) { 
780                              return ((URL) object).getFile(); 
781                          } 
782                      } 
783                      return null; 
784                  } 
785   
786                  public String getColumnName(int col) { 
787                      return names[col]; 
788                  } 
789   
790                  public Class getColumnClass(int c) { 
791                      return getValueAt(0, c).getClass(); 
792                  } 
793   
794                  public boolean isCellEditable(int row, int col) { 
795                      return false; 
796                  } 
797   
798                  public void setValueAt(Object aValue, int row, int col) { 
799                  } 
800              }; 
801   
802              table = new JTable(dataModel); 
803              TableColumn col = table.getColumn("#"); 
804              col.setMaxWidth(20); 
805              table.sizeColumnsToFit(0); 
806   
807              JScrollPane scrollPane = new JScrollPane(table); 
808              EmptyBorder eb = new EmptyBorder(5, 5, 2, 5); 
809              scrollPane.setBorder(new CompoundBorder(eb, new EtchedBorder())); 
810              add(scrollPane); 
811   
812              JPanel p1 = new JPanel(); 
813              JMenuBar menuBar = new JMenuBar(); 
814              menuBar.setBorder(new BevelBorder(BevelBorder.RAISED)); 
815              JMenu menu = (JMenu) menuBar.add(new JMenu("Add")); 
816              String items[] = {"File or Directory of Files", "URL"}; 
817              for (int i = 0; i < items.length; i++) { 
818                  JMenuItem item = menu.add(new JMenuItem(items[i])); 
819                  item.addActionListener(this); 
820              } 
821              p1.add(menuBar); 
822   
823              menuBar = new JMenuBar(); 
824              menuBar.setBorder(new BevelBorder(BevelBorder.RAISED)); 
825              menu = (JMenu) menuBar.add(new JMenu("Remove")); 
826              JMenuItem item = menu.add(new JMenuItem("Selected")); 
827              item.addActionListener(this); 
828              item = menu.add(new JMenuItem("All")); 
829              item.addActionListener(this); 
830              p1.add(menuBar); 
831   
832              loopB = addButton("loop", p1); 
833              loopB.setBackground(Color.gray); 
834              loopB.setSelected(true); 
835   
836              add("South", p1); 
837          } 
838   
839   
840          private JButton addButton(String name, JPanel p) { 
841              JButton b = new JButton(name); 
842              b.addActionListener(this); 
843              p.add(b); 
844              return b; 
845          } 
846   
847   
848          private void doFrame(String titleName) { 
849              int w = 500; 
850              int h = 130; 
851              JPanel panel = new JPanel(new BorderLayout()); 
852              JPanel p1 = new JPanel(); 
853              if (titleName.endsWith("URL")) { 
854                  p1.add(new JLabel("URL :")); 
855                  textField = new JTextField("http://foo.bar.com/foo.wav"); 
856                  textField.addActionListener(this); 
857              } else { 
858                  p1.add(new JLabel("File or Dir :")); 
859                  String sep = String.valueOf(System.getProperty("file.separator").toCharArray()[0]); 
860                  String text = null; 
861                  try { 
862                      text = System.getProperty("user.dir") + sep; 
863                  } catch (SecurityException ex) { 
864                      reportStatus(ex.toString()); 
865                      JavaSound.showInfoDialog(); 
866                      return; 
867                  } 
868                  textField = new JTextField(text); 
869                  textField.setPreferredSize(new Dimension(w - 100, 30)); 
870                  textField.addActionListener(this); 
871              } 
872              p1.add(textField); 
873              panel.add(p1); 
874              JPanel p2 = new JPanel(); 
875              applyB = addButton("Apply", p2); 
876              addButton("Cancel", p2); 
877              panel.add("South", p2); 
878              frame = new JFrame(titleName); 
879              frame.getContentPane().add("Center", panel); 
880              frame.pack(); 
881              Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); 
882              frame.setLocation(d.width / 2 - w / 2, d.height / 2 - h / 2); 
883              frame.setSize(w, h); 
884              frame.setVisible(true); 
885          } 
886   
887   
888          public void actionPerformed(ActionEvent e) { 
889              Object object = e.getSource(); 
890              if (object instanceof JTextField) { 
891                  applyB.doClick(); 
892              } else if (object instanceof JMenuItem) { 
893                  JMenuItem mi = (JMenuItem) object; 
894                  if (mi.getText().startsWith("File")) { 
895                      doFrame("Add File or Directory"); 
896                  } else if (mi.getText().equals("URL")) { 
897                      doFrame("Add URL"); 
898                  } else if (mi.getText().equals("Selected")) { 
899                      int rows[] = table.getSelectedRows(); 
900                      Vector tmp = new Vector(); 
901                      for (int i = 0; i < rows.length; i++) { 
902                          tmp.add(sounds.get(rows[i])); 
903                      } 
904                      sounds.removeAll(tmp); 
905                      tableChanged(); 
906                  } else if (mi.getText().equals("All")) { 
907                      sounds.clear(); 
908                      tableChanged(); 
909                  } 
910              } else if (object instanceof JButton) { 
911                  JButton button = (JButton) e.getSource(); 
912                  if (button.getText().equals("Apply")) { 
913                      String name = textField.getText().trim(); 
914                      if (name.startsWith("http") || name.startsWith("file")) { 
915                          try { 
916                              sounds.add(new URL(name)); 
917                          } catch (Exception ex) { 
918                              ex.printStackTrace(); 
919                          } 
920                          ; 
921                      } else { 
922                          loadJuke(name); 
923                      } 
924                      tableChanged(); 
925                  } else if (button.getText().equals("Cancel")) { 
926                      frame.dispose(); 
927                      frame = null; 
928                      errStr = null; 
929                      playbackMonitor.repaint(); 
930                  } else if (button.getText().equals("loop")) { 
931                      loopB.setSelected(!loopB.isSelected()); 
932                      loopB.setBackground(loopB.isSelected() ? Color.gray : Color.lightGray); 
933                  } 
934                  startB.setEnabled(sounds.size() != 0); 
935              } 
936          } 
937   
938          public void tableChanged() { 
939              table.tableChanged(new TableModelEvent(dataModel)); 
940          } 
941      }  // End JukeTable 
942   
943   
944      /** 
945       * Animation thread for when an audio file loads. 
946       */ 
947      class Loading extends Thread { 
948   
949          double extent; 
950          int incr; 
951   
952          public void run() { 
953              extent = 360.0; 
954              incr = 10; 
955              while (true) { 
956                  try { 
957                      sleep(99); 
958                  } catch (Exception ex) { 
959                      break; 
960                  } 
961                  playbackMonitor.repaint(); 
962              } 
963          } 
964   
965          public void render(Dimension d, Graphics2D g2) { 
966              if (isAlive()) { 
967                  FontRenderContext frc = g2.getFontRenderContext(); 
968                  TextLayout tl = new TextLayout("Loading", g2.getFont(), frc); 
969                  float sw = (float) tl.getBounds().getWidth(); 
970                  tl.draw(g2, d.width - sw - 45, d.height - 10); 
971                  double x = d.width - 33, y = d.height - 30, ew = 25, eh = 25; 
972                  g2.draw(new Ellipse2D.Double(x, y, ew, eh)); 
973                  g2.fill(new Arc2D.Double(x, y, ew, eh, 90, extent, Arc2D.PIE)); 
974                  if ((extent -= incr) < 0) { 
975                      extent = 350.0; 
976                  } 
977              } 
978          } 
979      } 
980   
981   
982      /** 
983       * Animation thread for the contributors of Java Sound. 
984       */ 
985      class Credits extends Thread { 
986   
987          int x; 
988          Font font16 = new Font("serif", Font.PLAIN, 16); 
989          String contributors = "Contributors : Kara Kytle, " + 
990                  "Jan Borgersen, " + "Brian Lichtenwalter"; 
991          int strWidth = getFontMetrics(font16).stringWidth(contributors); 
992   
993          public void run() { 
994              x = -999; 
995              while (!playbackMonitor.isShowing()) { 
996                  try { 
997                      sleep(999); 
998                  } catch (Exception e) { 
999                      return; 
1000                 } 
1001             } 
1002             for (int i = 0; i < 100; i++) { 
1003                 try { 
1004                     sleep(99); 
1005                 } catch (Exception e) { 
1006                     return; 
1007                 } 
1008             } 
1009             while (true) { 
1010                 if (--x < -strWidth) { 
1011                     x = playbackMonitor.getSize().width; 
1012                 } 
1013                 playbackMonitor.repaint(); 
1014                 try { 
1015                     sleep(99); 
1016                 } catch (Exception ex) { 
1017                     break; 
1018                 } 
1019             } 
1020         } 
1021  
1022         public void render(Dimension d, Graphics2D g2) { 
1023             if (isAlive()) { 
1024                 g2.setFont(font16); 
1025                 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
1026                         RenderingHints.VALUE_ANTIALIAS_OFF); 
1027                 g2.drawString(contributors, x, d.height - 5); 
1028             } 
1029         } 
1030     } 
1031  
1032  
1033     public static void main(String args[]) { 
1034         String media = "media"; 
1035         final Juke juke = new Juke(args.length == 0 ? media : args[0]); 
1036         juke.open(); 
1037         JFrame f = new JFrame("Juke Box"); 
1038         f.addWindowListener(new WindowAdapter() { 
1039             public void windowClosing(WindowEvent e) { 
1040                 System.exit(0); 
1041             } 
1042  
1043             public void windowIconified(WindowEvent e) { 
1044                 juke.credits.interrupt(); 
1045             } 
1046         }); 
1047         f.getContentPane().add("Center", juke); 
1048         f.pack(); 
1049         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 
1050         int w = 750; 
1051         int h = 340; 
1052         f.setLocation(screenSize.width / 2 - w / 2, screenSize.height / 2 - h / 2); 
1053         f.setSize(w, h); 
1054         f.setVisible(true); 
1055         if (args.length > 0) { 
1056             File file = new File(args[0]); 
1057             if (file == null && !file.isDirectory()) { 
1058                 System.out.println("usage: java Juke audioDirectory"); 
1059             } 
1060         } 
1061     } 
1062 } 
1063