
// PlaceClip.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Načte zvukový soubor jako klip a jednou jej přehraje.
   Jeho konec detekuje v pomocí volání metody update().

   Příkazový řádek může obsahovat nastavení hlasitosti a 
   polohy ve stereu:

        java PlaceClip <clip file> [ <volume> [<pan>] ]

   Hlasitost musí být mezi 0.0f a 1.0f nebo NO_VOL_CHANGE (-1.0f).

   Poloha ve stereu musí být mezi -1.0f a 1.0f nebo NO_PAN_CHANGE (0.0f).

   V J2EE 1.5 je k dispozici ovládací prvek BALANCE, který funguje
   podobně jako PAN (FloatControl.Type.BALANCE). PAN je stále k dispozici,
   ale nemusí fungovat pro stereo signály.

   Aplikace PlaceClip je rozšířením aplikace PlayClip. Změny se týkají
   dodatečných metod pro čtení nastavení hlasitosti a polohy ve stereu
   z příkazové řádky a metody pro úpravu ovládacích prvků klipu:
   setVolume() a setPan(). Metoda showControls() nabízí seznam
   dostupných ovládacích prvků klipu (který závisí na klipu
   směšovači).

  
   Změny 6. sprna 2004
     - měření doby trvání klipu pomocí getMicrosecondLength() v metodě loadClip()
     - uzavřen vstupní proud v metodě loadClip()

   Změny 16. září 2004
     - bug: má-li soubor typu WAV méně jak 1 sekundu, 
            nic se nepřehraje. Viz
            http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5085008

     Řešení: Upravit zvuk aby byl dělší jak 1 sekunda
        Voláme metodu checkDuration() třídy loadClip() pro ověření, 
        že trvání klipu není menší než 1 sekunda.

*/

import java.io.*;
import javax.sound.sampled.*;
import java.text.DecimalFormat;



public class PlaceClip implements LineListener
{
  private final static String SOUND_DIR = "Sounds/";

  private final static float NO_VOL_CHANGE = -1.0f;
  private final static float NO_PAN_CHANGE = 0.0f;

  private Clip clip = null;
  private AudioFormat format;
  private DecimalFormat df; 

  private float volume, pan;   // nastavení zadané na příkazovém řádku



  public PlaceClip(String[] args)
  {
    df = new DecimalFormat("0.#");  // 1 dm

    getSettings(args);   // získej nastavení hlasitosti a polohy ve stereu
                         // z příkazové řádky
    loadClip(SOUND_DIR + args[0]);

    // metody pro ovládacích prvků klipu 
    showControls();
    setVolume(volume);
    setPan(pan);

    play();

    // posečkej, až zvuk dohraje; jeho délku odhadujeme max. na 10 minut!
    try { 
      Thread.sleep(600000);   // 10 minut v milisekundách
    } 
    catch(InterruptedException e) 
    { System.out.println("Spánek byl přerušen"); }
  } // konec metody: PlaceClip()


  private void getSettings(String[] args)
  /* formát příkazového řádku:
        java PlaceClip <clip file> [ <volume> [<pan>] ]

     musíme tedy extrahovat nastavení hlasitosti a polohy ve stereu.     
  */
  {
    if (args.length == 1) {
      volume = NO_VOL_CHANGE;
      pan = NO_PAN_CHANGE;
      System.out.println("Nebyla zadána ani hlasitost ani poloha ve stereu");
    }
    else if (args.length == 2) {
      getVolumeSetting(args[1]);
      pan = NO_PAN_CHANGE;
      System.out.println("Nebyla zadána poloha ve stereu");
    }
    else if (args.length == 3) {
      getVolumeSetting(args[1]);
      getPanSetting(args[2]);
    }
    else {
      System.out.println("Použití: java PlaceClip <clip file> [ <volume> [<pan>] ]");
      System.exit(0);
    }
  }  // konec metody: getSettings()


  private void getVolumeSetting(String volStr)
  {
    try {
      volume = Float.parseFloat(volStr);
    }
    catch (NumberFormatException ex){ 
      System.out.println("Nesprávný formát hlasitosti"); 
      volume = NO_VOL_CHANGE;
    }
    if (volume == NO_VOL_CHANGE)
      System.out.println("Beze změny hlasitosti"); 
    else if ((volume >= 0.0f) && (volume <= 1.0f))
      System.out.println("Nastavení hlasitosti: " + volume); 
    else {
      System.out.println("Hlasitost mimo rozsah (0-1.0f); beze změny"); 
      volume = NO_VOL_CHANGE;
    }
  }  // konec metody: getVolumeSetting()


  private void getPanSetting(String panStr)
  {
    try {
      pan = Float.parseFloat(panStr);
    }
    catch (NumberFormatException ex){ 
      System.out.println("Nesprávný formát polohy ve stereu"); 
      pan = NO_PAN_CHANGE;
    }
    if (pan == NO_PAN_CHANGE)
      System.out.println("Poloha ve stereu beze změn"); 
    else if ((pan >= -1.0f) && (pan <= 1.0f))
      System.out.println("Nastavení polohy ve stereu: " + pan); 
    else {
      System.out.println("Poloha ve stereu mimo rozsah (-1.0f - 1.0f); beze změny"); 
      pan = NO_PAN_CHANGE;
    }
  }  // konec metody: getPanSetting()


  private void loadClip(String fnm)
  {
    try {
      // připoj zvukový proud k souboru s audio-klipem
      AudioInputStream stream = AudioSystem.getAudioInputStream(
                          getClass().getResource(fnm) );

      format = stream.getFormat();    // objekt format je globální, neboť se používá v metodě setPan()
      System.out.println("Audio formát: " + format);

      // převeď data ve formátu ULAW/ALAW do formátu PCM
      if ( (format.getEncoding() == AudioFormat.Encoding.ULAW) ||
           (format.getEncoding() == AudioFormat.Encoding.ALAW) ) {
        AudioFormat newFormat = 
           new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                                format.getSampleRate(),
                                format.getSampleSizeInBits()*2,
                                format.getChannels(),
                                format.getFrameSize()*2,
                                format.getFrameRate(), true);  // způsob uložení dat: big endian
        // aktualizuj podrobné údaje ohledně datového proudu a formátu
        stream = AudioSystem.getAudioInputStream(newFormat, stream);
        System.out.println("Převedený audio formát" + newFormat);
        format = newFormat;
      }

      DataLine.Info info = new DataLine.Info(Clip.class, format);

      // ujisti se, že zvukový systém podporuje datové linky
      if (!AudioSystem.isLineSupported(info)) {
        System.out.println("Nepodporovaný formát souboru: " + fnm);
        System.exit(0);
      }

      // získej zdroj linky klipu 
      clip = (Clip) AudioSystem.getLine(info);

      // naslouchej událostem klipu
      clip.addLineListener(this);

      clip.open(stream);    // otevři zvukový soubor jako klip
      stream.close(); // se vstupním proudem jsme skončili  // nový

      checkDuration();
    } // konec metody: try block

    catch (UnsupportedAudioFileException audioException) {
      System.out.println("Nepodporovaný zvukový soubor: " + fnm);
      System.exit(0);
    }
    catch (LineUnavailableException noLineException) {
      System.out.println("Zvuková linka není k dispozici : " + fnm);
        System.exit(0);
    }
    catch (IOException ioException) {
      System.out.println("Nemohu přečíst soubor: " + fnm);
      System.exit(0);
    }
    catch (Exception e) {
      System.out.println("Problém s " + fnm);
      System.exit(0);
    }
  } // konec metody: loadClip()


  private void checkDuration()
  {
    // délka klipu (v sekundách)
	double duration = clip.getMicrosecondLength()/1000000.0;  // new
    if (duration <= 1.0) {
      System.out.println("WARNING. Duration <= 1 sekunda : " + df.format(duration) + " sekund");
      System.out.println("         Je možné, že nedojde v J2SE 1.5 nedojde k přehrátí -- zvětšete jej");
    }
    else
      System.out.println("Délka: " + df.format(duration) + " sekund");
  }  // konec metody: checkDuration()



  private void showControls()
  // zobrazí všechny ovládací prvky dostupné pro tento klip
  {
    if (clip != null) {
      Control cntls[] = clip.getControls(); 
      for(int i=0; i<cntls.length; i++)
        System.out.println( i + ".  " + cntls[i].toString() );
    }
  }  // konec metody: showControls()


  private void setVolume(float volume)
  /* Hlasitost mezi 0.0f a 1.0f nebo NO_VOL_CHANGE (-1.0f)
     Zesílení se počítá pomocí lineární škály, ačkoliv
     by asi bylo lepší použít logaritmickou, protože 
     tak se zesílení vyjadřuje v decibelech.
  */
  {
    if ((clip != null) && (volume != NO_VOL_CHANGE)) {
      if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
        FloatControl gainControl =
           (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);

        float range = gainControl.getMaximum() - gainControl.getMinimum();
        float gain = (range * volume) + gainControl.getMinimum();
        System.out.println("Hlasitost: "+volume+"; Nové zesílení: " + gain);
        gainControl.setValue(gain);
      }
      else
        System.out.println("Ovládací prvky pro hlasitost nejsou dostupné");
    }
  }  // konec metody: setVolume()


  private void setPan(float pan)
  /* Poloha ve stereu je mezi -1.0f a 1.0f nebo NO_PAN_CHANGE (0.0f).

     Pokud změna polohy ve stereu není k dispozici, pokusíme se použít
     ovládací prvek vyváření.

     Výchozí směšovač v J2SE 1.5 nepodporuje změnu polohy ve stereu,
     ovšem má ovládání vyvážení. Nicméně tento ovládací prvek je
     k dispozici jen pokud je vstup ve stereu.
  */
  {
    if ((clip == null) || (pan == NO_PAN_CHANGE))
      return;   // nedělej nic

    if (clip.isControlSupported(FloatControl.Type.PAN)) {
      FloatControl panControl =
         (FloatControl) clip.getControl(FloatControl.Type.PAN);
      // System.out.println("Rozsah sterea: " + pan);
      panControl.setValue(pan);
    }
    else if (clip.isControlSupported(FloatControl.Type.BALANCE)) {
      FloatControl balControl =
         (FloatControl) clip.getControl(FloatControl.Type.BALANCE);
      // System.out.println("Rozsah vyvážení: " + pan);
      balControl.setValue(pan);
    }
    else {
      System.out.println("Ovládací provky pro pohyb ve stereu ani pro vyvážení nejsou k dispozici");
      if (format.getChannels() == 1)   // vstup v módu mono
        System.out.println("Daný zvukový záznam je v módu mono; převeďte jej prosím na stereo");
    }
  }  // konec metody: setPan()



  private void play()
  { if (clip != null)
      clip.start();   // spusť přehrávání
  }


  public void update(LineEvent lineEvent)
  // volá se, když linka klipu detekuje událost otevření, uzavření, začátku či konce
  {
    // došel klip na konec?
    if (lineEvent.getType() == LineEvent.Type.STOP) {
      // System.out.println("Končím...");
      clip.stop();
      lineEvent.getLine().close();
      System.exit(0);    // v J2SE 1.4.2 nebo dříve nutné
    }
  } // konec metody: update()


  // --------------------------------------------------

  public static void main(String[] args)
  {  new PlaceClip(args);  
     System.exit(0);    // do J2SE 1.4.2. musíme program ukončit explicitně
  }

} // konec třídy: PlaceClip
