//[skeleton0.
package auditory.described;

import java.awt.event.*;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.sound.midi.*;

import event.*;


/**
 * An encapsulation of a music score
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Score implements MetronomeListener
{
//]skeleton0.
//[skeleton1
    private Hashtable<Part, String>            parts;
    private int                                timeSignatureDenominator, 
                                               timeSignatureNumerator,
                                               millisPerMeasure;
//]skeleton1
//[declarations
    private Hashtable<String, Instrument>      instruments;
    private Hashtable<Part, MidiChannel>       channelTable;
    private Metronome                          metronome;    
    private MidiChannel[]                      channels;
//]declarations
    
//[constructor.

    /**
     * Default Constructor
     */
    public Score() throws Exception 
    {
       Instrument[]    loaded;
       Soundbank       defaultSB;
       Synthesizer     synthesizer;


       metronome    = new Metronome(10);
       metronome.addListener(this);       
              
       parts        = new Hashtable<Part, String>();       
       instruments  = new Hashtable<String, Instrument>();
       channelTable = new Hashtable<Part, MidiChannel>();
       
       synthesizer = MidiSystem.getSynthesizer();
       synthesizer.open();
       defaultSB = synthesizer.getDefaultSoundbank();

       synthesizer.loadAllInstruments(defaultSB);

       channels    = synthesizer.getChannels();

       loaded = synthesizer.getLoadedInstruments();
       for (int i=0; i<loaded.length; i++)
       {
          instruments.put(loaded[i].getName().trim(), loaded[i]);
          System.out.println("|"+loaded[i].getName().trim()+"|");
          
       }
       
    }
//]constructor.
//[handleTick.

    /**
     * Handle a Metronome tick
     * (required by MetronomeListener)
     *
     * This method tells each Part to render itself.  In addition, if
     * there are no Part objects listening to the Metronome, this
     * method stops the Metronome
     *
     * @param millis   The number of milliseconds since the Metronome started
     */
    public void handleTick(int millis)
    {
       Enumeration<Part>           e;
       MidiChannel                 channel;
       Part                        part;
       

       e = parts.keys();
       while (e.hasMoreElements())
       {
          part       = e.nextElement();
          channel    = channelTable.get(part);
          part.render(channel);          
       }
       

       if (metronome.getNumberOfListeners() == 1)
       {
          metronome.stop();          
       }
    }
//]handleTick.
//[skeleton2.
    
    /**
     * Add a Part to this Score
     *
     * @param part   The Part to add
     */
    public void addPart(Part part, String instrument)
    {
       parts.put(part, instrument);
    }

    /**
     * Remove a Part from this Score
     *
     * @param part   The Part to remove
     */
    public void removePart(Part part)
    {
       parts.remove(part);
    }    
//]skeleton2.

//[setters    

    /**
     * Set the tempo for this Part
     *
     * @param millisPerMeasure  The tempo (in milliseconds per measure)
     */
    public void setTempo(int millisPerMeasure)
    {
       this.millisPerMeasure = millisPerMeasure;       
    }
    

    /**
     * Set the time signature for this Part
     *
     * @param numerator    The numerator of the time signature
     * @param denominator  The denominator of the time signature
     */
    public void setTimeSignature(int numerator, int denominator)
    {
       this.timeSignatureNumerator   = numerator;
       this.timeSignatureDenominator = denominator;
    }
//]setters
//[play.

    /**
     * Play this  Score
     */
    public void play()
    {
       Enumeration<Part>           e;
       Instrument                  instrument;       
       int                         i;       
       MidiChannel                 channel;       
       String                      name;       
       Patch                       patch;       
       Part                        part;
       

       e = parts.keys();
       i = 0;       

       while (e.hasMoreElements())
       {
          part       = e.nextElement();
          name       = parts.get(part);          
          instrument = instruments.get(name);
          System.out.println("|"+name+"|"+"\t"+instrument);
          
          
          
          // Have the channel use the appropriate instrument
          if (instrument == null)
          {
             channels[i].programChange(0, 0);
          }
          else
          {
             patch = instrument.getPatch();
             channels[i].programChange(patch.getBank(), 
                                       patch.getProgram());
          }
          channelTable.put(part, channels[i]);             
          
          
          // Get the Part ready
          part.upbeat(metronome);
          part.setTimeSignature(timeSignatureNumerator, 
                                timeSignatureDenominator);
          part.setTempo(millisPerMeasure);
          

          i++;          
       }
              
       
       // Start the metronome
       metronome.start();
    }
//]play.
//[skeleton3.

}

//]skeleton3.
