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

/* Přehraje midi-sekvenci, a dovolí úpravu její hlasitosti.
   V této ukázkové aplikaci se během přehrávání hlasitost 
   pomalu snižuje až k 0.

   S hlasitostí pracujeme voláním ovládacích prvků kanálu syntezátoru,
   konkrétně ovládací prvek 7 (VOLUME_CONTROLLER) pro řízení
   nastavení hlasitosti kanálu.
   
   Změnu hlasitosti provádí zvláštní vlákno VolChanger,
   spuštěné v metodě main(). Volání metody startVolChanger() 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()

   Změny 16. září 2004
     - získání sekvenceru jeho explicitním vyžádáním v metodě obtainSequencer()

*/

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



public class FadeMidi implements MetaEventListener 
{
  // konstanta meta události midi pro oznámení konce stopy 
  private static final int END_OF_TRACK = 47;

  private static final int VOLUME_CONTROLLER = 7;

  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 FadeMidi(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: FadeMidi()


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

      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 Sequencer obtainSequencer()
  /* Tato metoda řeší cyhbu v J2SE 1.5.0, kdy dojde k 
     získání sekvenceru metodou getSequencer(), ale 
     není možné měnit jeho hlasitost. */
  {
    // return MidiSystem.getSequencer();   
    // v poho v J2SE 1.4.2, ale ne v J2SE 1.5.0

    MidiDevice.Info[] mdi = MidiSystem.getMidiDeviceInfo();
    int seqPosn = -1;
    for(int i=0; i < mdi.length; i++) {
      System.out.println(mdi[i].getName());
      // if (mdi[i].getName().contains("Sequencer")) {
      if (mdi[i].getName().indexOf("Sequencer") != -1) {
        seqPosn = i;    // sekvencer nalezen
        System.out.println("  Sekvencer nalezen");
      }
    }

    try {
      if (seqPosn != -1)
        return (Sequencer) MidiSystem.getMidiDevice( mdi[seqPosn] );
      else
        return null;
    }
    catch(MidiUnavailableException e)
    { return null; }
  }  // konec metody: obtainSequencer()


  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();
        // showChannelVolumes();
      }
      catch (InvalidMidiDataException e) {
        System.out.println("Poškozený/neplatný soubor midi: " + filename);
        System.exit(0);
      }
    }
  } // konec metody: play()


  private void showChannelVolumes()
  // vypiš úroveň hlasitosti všech kanálů syntezátoru
  {
    System.out.println("Kanály syntezátoru: " + channels.length);

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


  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 úpravu hlasitosti ------------------

  public void startVolChanger(VolChanger vc)
  /* Spustí vlákno pro úpravu hlasitosti 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.
  */
  {  vc.startChanging( (int)(seq.getMicrosecondLength()/1000) );  }


  public int getMaxVolume()
  // vrať maximální úroveň ze všech ovládacích prvků pro hlasitost
  {  
    int maxVol = 0;
    int channelVol;
    for (int i=0; i < channels.length; i++) {
      channelVol = channels[i].getController(VOLUME_CONTROLLER);
      if (maxVol < channelVol)
        maxVol = channelVol;
    }
    return maxVol; 
 } // konec metody: getVolume()


  public void setVolume(int vol)
  // nastav úroveň všech ovládacích provků hlasitosti na hodnotu vol
  {      
    for (int i=0; i < channels.length; i++)
      channels[i].controlChange(VOLUME_CONTROLLER, vol);

    // showChannelVolumes();
  }

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

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

    player.startVolChanger(vc);  // zahaj úpravu hlasitosti
  } // konec metody: main()

} // konec třídy: FadeMidi class
