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

/* Přehrává MIDI-sekvenci a umožňuje nastavení její polohy ve stereu.
    V této ukázkové aplikaci zvuk cyklicky přechází z levého do pravého
    reproduktoru.

   Nastavení hlasitosti se provádí voláním ovládacích prvků 
   syntezátoru, konkrétně ovládací prvek 10 (PAN_CONTROLLER), 
   pro řízení natavení polohy ve stereu kanálu.

   Alternativou by bylo použití ovládací prvku vyvážení (BALANCE_CONTROLLER), 
   na testovacím stroji to však nefungovalo.

   Změnu hlasitosti provádí zvláštní vlákno PanChanger,
   spuštěné v metodě main(). Volání metody startPanChanger() třídy
   FadeMidi spustí aplikaci změn hlasitosti. 

   Třída FadeMidi (a PanMidi) je rozšířenou verzí třídy PlayMidi.  

   Změny 6. srpna 2004
     - přidáno volání synthesizer.open() do metody initSequencer()

*/

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


public class PanMidi implements MetaEventListener 
{
  // private static final int BALANCE_CONTROLLER = 8;   // nefunguje (?)
  private static final int PAN_CONTROLLER = 10;

  // konstanta meta události midi pro signalizaci konce stopy
  private static final int END_OF_TRACK = 47;

  private final static String SOUND_DIR = "Sounds/";


  private Sequencer sequencer;
  private Synthesizer synthesizer;
  private Sequence seq;
  private String filename;
  private DecimalFormat df; 

  // uchovává kanály syntezátoru
  private MidiChannel[] channels;


  public PanMidi(String fnm) 
  {
    df = new DecimalFormat("0.#");  // 1 dm

    filename = SOUND_DIR + fnm;
    initSequencer();
    loadMidi(filename);
    play();

    /* Spánek není třeba pro udržení objektu naživu, poněvadž
       vlákno VolChanger na něj odkazuje. */
  }  // konec metody: PanMidi()


  private void initSequencer() 
  /* Připraví MIDI-sekvencer, naslouchač meta-událostí sekvenceru
     a jeho syntezátor. */
  {
    try {
      sequencer = MidiSystem.getSequencer();

      if (sequencer == null) {
        System.out.println("Nemohu získat sekvencer");
        System.exit(0);
      }

      sequencer.open();
      sequencer.addMetaEventListener(this);

      // sekvencer možná není totožný se syntezátorem
      // takže napoj sekvencer --> syntezátor (k tomu je třeba J2SE 1.5)
      if (!(sequencer instanceof Synthesizer)) {
        System.out.println("Spojuji MIDI-sekvencer a syntezátor");
        synthesizer = MidiSystem.getSynthesizer();
        synthesizer.open();  // nový
        Receiver synthReceiver = synthesizer.getReceiver();
        Transmitter seqTransmitter = sequencer.getTransmitter();
        seqTransmitter.setReceiver(synthReceiver);
      }
      else 
        synthesizer = (Synthesizer) sequencer;
    }
    catch (MidiUnavailableException e){
      System.out.println("Žádný sekvencer není k dispozici");
      System.exit(0);
    }
  } // konec metody: initSequencer()


  private void loadMidi(String fnm)
  { seq = null;
    try {
      seq = MidiSystem.getSequence( getClass().getResource(fnm) );
      double duration = ((double) seq.getMicrosecondLength()) / 1000000;
      System.out.println("Délka: " +  df.format(duration) + " sekund");
    }
    catch (InvalidMidiDataException e) {
      System.out.println("Nečitelný/nepodporovaný midi-soubor: " + fnm);
      System.exit(0);
    }
    catch (IOException e) {
      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: loadMidi()



  private void play()
  { 
    if ((sequencer != null) && (seq != null)) {
      try {
        sequencer.setSequence(seq);  // umísti MIDI do sekvenceru
        sequencer.start();   // přehrej sekvenci
        channels = synthesizer.getChannels();
        // showChannelPans();
      }
      catch (InvalidMidiDataException e) {
        System.out.println("Poškozený/neplatný soubor midi: " + filename);
        System.exit(0);
      }
    }
  } // konec metody: play()


  private void showChannelPans()
  // zobrazí úrovně polohy ve stereu pro všechny kanály syntezátoru
  {
    System.out.println("Počet kanálů syntezátoru: " + channels.length);

    System.out.print("Pans: {");
    for (int i=0; i < channels.length; i++)
      System.out.print( channels[i].getController(PAN_CONTROLLER) + " ");
    System.out.println("}");
  }  // konec metody: showChannelPans()


  public void meta(MetaMessage event) 
  /* Tuto metodu spouští meta-události. 
     Meta-událost konec-stopy signalizuje, že sekvence skončila
  */
  { if (event.getType() == END_OF_TRACK) {
      System.out.println("končíme");
      close();
      System.exit(0);  // v J2SE 1.4.2 nebo dříve nutné
    }
  } // konec metody: meta()


  private void close()
  // Uzavře sekvencer a syntezátor
  {
    if (sequencer != null) {
      if (sequencer.isRunning())
        sequencer.stop();
       
      sequencer.removeMetaEventListener(this);
      sequencer.close();

      if (synthesizer != null)
        synthesizer.close();
    }
  }  // konec metody: close()

  
  // ------------------- metody pro změnu polohy ve stereu ------------------


  public void startPanChanger(PanChanger pc)
  /* Spustí vlákno pro úpravu polohy ve stereu a dodá dobu trvání MIDI-sekvence
     v milisekundách.
     Doba trvání je nutná pro odhad, jak často se má nová hodnota hlasistosti
     aktualizovat.
  */
  {  pc.startChanging( (int)(seq.getMicrosecondLength()/1000) );  }


  public int getMaxPan()
  // vrať max. hodnotu pro pohyb ve stereu všech příslušných ovládacích prvků
  {  
    int maxPan = 0;
    int channelPan;
    for (int i=0; i < channels.length; i++) {
      channelPan = channels[i].getController(PAN_CONTROLLER);
      if (maxPan < channelPan)
        maxPan = channelPan;
    }
    return maxPan; 
 } // konec metody: getMaxPan()


  public void setPan(int panVal)
  // nastav hodnotu všech ovládacích prvků pro pohyb ve stereu na panVal
  {      
    for (int i=0; i < channels.length; i++)
      channels[i].controlChange(PAN_CONTROLLER, panVal);
  }

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

  public static void main(String[] args) 
  {
    if (args.length != 1) {
      System.out.println("Použití: java PanMidi <midi file>");
      System.exit(0);
    }
    // připrav objekty pro přehrávání a změnu polohy ve stereu
    PanMidi player = new PanMidi(args[0]);
    PanChanger pc = new PanChanger(player);

    player.startPanChanger(pc);  // zahaj úpravu polohy ve stereu
  } // konec metody: main()

} // konec třídy: PanMidi