/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                       *
 *   JavaWorld Library, Copyright 2011 Bryan Chadwick                    *
 *                                                                       *
 *   FILE: ./world/sound/tunes/MusicBox.java                             *
 *                                                                       *
 *   This file is part of JavaWorld.                                     *
 *                                                                       *
 *   JavaWorld is free software: you can redistribute it and/or          *
 *   modify it under the terms of the GNU General Public License         *
 *   as published by the Free Software Foundation, either version        *
 *   3 of the License, or (at your option) any later version.            *
 *                                                                       *
 *   JavaWorld is distributed in the hope that it will be useful,        *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
 *   GNU General Public License for more details.                        *
 *                                                                       *
 *   You should have received a copy of the GNU General Public License   *
 *   along with JavaWorld.  If not, see <http://www.gnu.org/licenses/>.  *
 *                                                                       *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package world.sound.tunes;

import java.util.ArrayList;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Synthesizer;

/** Represents/initializes the MIDI synthesizer and manages the
 *    channels/instruments in the MIDI program.  Based in part on a
 *    class originally designed by Viera K. Proulx. */
public class MusicBox implements SoundConstants{

    /** MIDI synthesizer that plays notes */
    private Synthesizer synth; 

    /** MIDI channels that are currently assigned to the synthesizer */
    private MidiChannel[] channels;

    /** Is this MusicBox ready to play notes? (initialized correctly) */
    private boolean READY = false;

    /** The default constructor just initializes the MIDI synthesizer
     *     and sets the default program for the instruments. */
    public MusicBox(){
        initMusic();
        initChannels();
    }  

    /** Up to 16 instruments (really 24 I think, but not exactly sure) */
    //private final static int NUM_CHANNELS = 16;
    
    /** The MIDI synthesizer can also be initialized with a list of instrument
     *     numbers rather than setting the default program. */
    public MusicBox(int ... instruments){
        initMusic();
        initChannels(instruments);
    }

    /** Initialize this MusicBox (instruments/program/channels) */
    protected void initMusic(){
        try{
            this.synth = MidiSystem.getSynthesizer();
            this.synth.open();

            this.synth.loadAllInstruments(this.synth.getDefaultSoundbank());
            this.channels = this.synth.getChannels();

            if (this.channels != null){
                this.initChannels();
                this.READY = true;
            }
        }catch(javax.sound.midi.MidiUnavailableException e){
            System.err.println("MidiUnavailableException: " +
                    e.getMessage());
            this.READY = false;
        }catch(NullPointerException e){
            System.err.println("Midi Initialization Error: " +
                    e.getMessage());
            this.READY = false;
        }
    }

    /** Initialize the program to the default set of instruments defined in 
     *    {@link world.sound.tunes.SoundConstants}. */
    public void initChannels(){
        this.initChannels(SoundConstants.INSTRUMENTS);
    }

    /** Initialize the MIDI channels to the given set of instruments.  You
     *   may give up to 16 instrument numbers (as instrument numbers defined in
     *   {@link world.sound.tunes.SoundConstants}) to assign to the channels. */
    public void initChannels(int ... instruments){
        for (int i = 0; i < this.channels.length && i < instruments.length; i++){
            this.channels[i].programChange(instruments[i]-1);
        }
    }

    /** Produce the instrument currently assigned to the given channel. */
    public int getProgram(int channel){
        return this.channels[channel].getProgram();
    }

    /** Play all tunes in the given list of <code>Tune</code>s */
    public void playOn(ArrayList<Tune> tunes){
        for(int i = 0; i < tunes.size(); i++)
            this.playTune(tunes.get(i));
    }

    /** Play the given tune on the channel assigned to it. */
    public void playTune(Tune tune){
        for(int i = 0; i < tune.chord.notes.size(); i++){
            Note n = tune.chord.notes.get(i);
            if(!n.isSilent()){
                this.channels[tune.channel].noteOn(n.getPitch(), n.getVelocity());
                n.nextBeat();
            }
        }
    }

    /** Stop playing all tunes in the given list of <code>Tune</code>s. */
    public void playOff(ArrayList<Tune> tunes){
        for(int i = 0; i < tunes.size(); i++)
            this.stopTune(tunes.get(i));
    }  

    /** Stop playing the given tune on the channel assigned to it. */
    public void stopTune(Tune tune){
        for(int i = 0; i < tune.chord.notes.size(); i++){
            Note n = tune.chord.notes.get(i);
            this.channels[tune.channel].noteOff(n.getPitch(), n.getVelocity());
        }
    }
    
    /** Has this music box been initialized? */
    public boolean isReady(){ return READY; }
}