/*
 * Synthesizer.java
 *
 * Created on June 7, 2006, 9:04 PM
 *
 */

package control;

import control.*;
import java.util.ArrayList;
import java.util.List;
import javax.sound.midi.Instrument;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Soundbank;
import javax.sound.midi.Synthesizer;

/**
 * This class simplifies access to the midi sound system.
 * @author jbf
 */
public class MusicalInstrument {
    
    /** Creates a new instance of MusicalInstrument */
    public MusicalInstrument() {
        open();
    }
    
    MidiChannel mc;
    Sequence sequence;
    
    Synthesizer synthesizer;
    Instrument instruments[];
    MidiChannel[] midiChannels;
    
    // keep track of allocated channels so each instrument gets its own channel.
    static int allocatedChannels=0;
    
    private void open() {
        try {
            if (synthesizer == null) {
                if ((synthesizer = MidiSystem.getSynthesizer()) == null) {
                    System.out.println("getSynthesizer() failed!");
                    return;
                }
            }
            synthesizer.open();
        } catch (Exception ex) { ex.printStackTrace(); return; }
        
        Soundbank sb = synthesizer.getDefaultSoundbank();
        if (sb != null) {
            instruments = synthesizer.getDefaultSoundbank().getInstruments();
            System.out.println( instruments[0].getName() );
        }
        
        midiChannels = synthesizer.getChannels();
        if ( allocatedChannels==midiChannels.length ) allocatedChannels=0;
        
        mc= midiChannels[allocatedChannels++];
        
        setInstrumentType(0);
        
    }
    
    /**
     * Set the instrument type.
     * @param i the index of the instrument to load.  The indeces depend on the sound system, so this function will
     * yeild different results on different machines.
     */
    public void setInstrumentType( int i ) {
        i= i > (instruments.length-1) ? (instruments.length-1) : i;
        Instrument instrument= instruments[i];
        synthesizer.loadInstrument(instrument);
        mc.programChange(instrument.getPatch().getProgram());
    }
    
    /**
     * Return all instrument types as a List of Strings.
     * @return a List of all the available instrument types.
     */
    public List getInstrumentTypes() {
        ArrayList result= new ArrayList();
        for ( int i=0; i<instruments.length; i++ ) {
            result.add( instruments[i].getName());
        }
        return result;
    }
    
    /**
     * Set the instrument type by name
     * @param name set the instrument type by name.  The list of instrument types is searched to find the specified
     * name, and if found, that instrument is loaded.  Otherwise, an undetermined instrument will be used 
     * instead.
     */
    public void setInstrumentType( String name ) {
        int i;
        for ( i=0; i<instruments.length; i++ ) {
            if ( instruments[i].getName().equalsIgnoreCase(name) ) break;
        }
        i= i > (instruments.length-1) ? (instruments.length-1) : i;
        Instrument instrument= instruments[i];
        synthesizer.loadInstrument(instrument);
        mc.programChange(instrument.getPatch().getProgram());
    }
    
    /**
     * plays a note on the instrument, returning immediately.  So you must put in delays between the
     * notes.  For example:
     * <pre>
     *   MusicalInstrument myInstrument= Chess.getMusicalInstrument();
     *   for ( int i=0; i<4; i++ ) {
     *      myInstrument.playNote( 60, 100, 200 );
     *      Chess.sleep(250);
     *   }
     * </pre>
     * @param noteNumber from 0-127.  60 is middle C.
     * @param velocity the bigger the number, the louder the note is played.
     * @param millis duration of the note in milliseconds.
     */
    public void playNote( final int noteNumber, int velocity, final int millis ) {
        mc.noteOn( noteNumber, velocity );
        Runnable run= new Runnable() {
            public void run() {
                try {
                    Thread.sleep(millis);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
                mc.noteOff(noteNumber);
            }
        };
        new Thread(run).start();
    }
    

}
