/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                       *
 *   JavaWorld Library, Copyright 2011 Bryan Chadwick                    *
 *                                                                       *
 *   FILE: ./world/sound/tunes/Note.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;

/** Represents a single note which includes a pitch and duration.  Notes may
 *    be created/converted to and from a String representation that includes
 *    the <tt>name</tt>, <tt>octave</tt>, <tt>modifier</tt>, and
 *    <tt>duration</tt>, where: 
 *   <ul>
 *     <li> Name is one of <i class='str'>A</i>, <i class='str'>B</i>,
 *     <i class='str'>C</i>, <i class='str'>D</i>, <i class='str'>E</i>,
 *     <i class='str'>F</i>, or <i class='str'>G</i></li>
 *     <li> Octave is the keyboard octave: a single digit</li>
 *     <li> Modifier is one of:
 *         <ul>
 *           <li><i class='str'>n</i> : natural,</li> 
 *           <li><i class='str'>s</i> : sharp,</li> 
 *           <li><i class='str'>f</i> : flat</li>
 *         </ul>
 *     </li>
 *     <li> Duration is the number of <i>beats</i> in the range [0..16]</li>
 *   </ul>
 * 
 * <p>
 *    As an example, <tt class='str'>"C6n4"</tt> represents a C natural, in
 *    the 6th octave (Note 72), that will play for 4 beats
 * </p>
 *   
 *  Based in part on a class originally designed by Viera K. Proulx. 
 */
public class Note{
    /** The default velocity of Notes */
    static int DEFAULT_VELOCITY = 60;

    /** The <code>MIDI</code> pitch of this Note. */
    private int pitch;

    /** The duration of this Note. Typically a maximum of 16 beats for
     *    a whole note in a 4/4 measure (though this depends on the tempo) */
    private int duration;

    /** A letter that represents the name of this Note ('A'..'G') */
    private char noteName;

    /** This Note's modifier: 's' = sharp, 'f' = flat, 'n' = natural */
    private char modifier;

    /** The <code>MIDI</code> octave on a piano keyboard (0 - 8) */
    private int octave;

    /** The velocity (volume) of this note. */
    private int velocity;
    
    /** The distance of Notes from the note 'A'. */
    private static int[] OFFSETS = new int[]{0, 2, 3, 5, 7, 8, 10};

    /** The names of notes; lower case letters denote a 'sharp' modifier.
     *    The first 'E' Note, as in "E0", is MIDI note 0 which is unused. */
    private static String OFFSET_NAMES = "EFfGgAaBCcDd";

    /** Create a Note with the given MIDI pitch, with a default duration
     *    of 1 and a default velocity. */
    public Note(int pitch){
        this(pitch, 1);
    }
    
    /** Create a Note with the given MIDI pitch and duration, and
     *    the default velocity. */
    public Note(int pitch, int duration){
        this(pitch, duration, DEFAULT_VELOCITY);
    }  
    
    /** Create a Note with the given MIDI pitch, duration, and velocity. */
    public Note(int pitch, int duration, int velocity){
        this.pitch = Note.pitch(pitch);
        this.duration = Note.duration(duration);
        this.velocity = velocity;
        
        if(pitch == 0){
            this.octave = 0;
            this.noteName = 0;
            this.modifier = 'n';
        }else{
            this.octave = Note.pitchToOctave(pitch);
            this.noteName = OFFSET_NAMES.charAt((pitch + 8) % 12);
            if(Character.isLowerCase(this.noteName)){
                this.modifier = 's';
                this.noteName = Character.toUpperCase(this.noteName);
            }else
                this.modifier = 'n';
        }
    }  

    /** Create a Note from the description given as a String, and the
     *    default velocity. */
    public Note(String snote){
        this(snote, DEFAULT_VELOCITY);
    }
        
    /** Create a Note from the description given as a String. The encoding
     *    is either 4 or 5 characters long and  includes
     *    the <tt>name</tt>, <tt>octave</tt>, <tt>modifier</tt>, and
     *    <tt>duration</tt>, where: 
     *   <ul>
     *     <li> Name is one of <i class='str'>A</i>, <i class='str'>B</i>,
     *     <i class='str'>C</i>, <i class='str'>D</i>, <i class='str'>E</i>,
     *     <i class='str'>F</i>, or <i class='str'>G</i></li>
     *     <li> Octave is the keyboard octave: a single digit</li>
     *     <li> Modifier is one of:
     *         <ul>
     *           <li><i class='str'>n</i> : natural,</li> 
     *           <li><i class='str'>s</i> : sharp,</li> 
     *           <li><i class='str'>f</i> : flat</li>
     *         </ul>
     *     </li>
     *     <li> Duration is the number of <i>beats</i> in the range [0..16]</li>
     *   </ul>
     * 
     * <p>
     *    As an example, <tt class='str'>"C6n4"</tt> represents a C natural, in
     *    the 6th octave (Note 72), that will play for 4 beats
     * </p> */
    public Note(String snote, int velocity){
        this.velocity = Note.velocity(velocity);
        this.noteName = Note.noteName(snote.charAt(0));
        this.modifier = Note.modifier(snote.charAt(2));
        this.octave = Note.octave(snote.charAt(1)-'0');
        this.duration = snote.charAt(3)-'0';
        if(snote.length() == 5)
            this.duration = 10 * this.duration + (snote.charAt(4)-'0');
        this.pitch = Note.computePitch(this.noteName, this.octave, this.modifier);
    }

    /** Create a Note by copying the given Note. */
    private Note(Note that){
        this(that.pitch, that.duration, that.velocity);
    }
    
    /** Make a copy of this Note. */
    public Note copy(){ return new Note(this); }

    /** Get the Pitch of this Note. */
    public int getPitch(){ return this.pitch; }

    /** Get the Duration of this Note. */
    public int getDuration(){ return this.duration; }
    
    /** Get the Velocity of this Note. */
    public int getVelocity(){ return this.velocity; }

    /** Does the given Note represent the same Note as this one? */
    public boolean sameNote(Note that){
        return (this.pitch == that.pitch &&
                this.duration == that.duration);
    }

    /** Has this note completed its duration? */
    public boolean isSilent(){ return this.duration == 0; }

    /** Decrement the duration of this Note. */
    public void nextBeat(){
        if (this.duration > 0)
            this.duration--;
        else
            this.duration = 0;
    }

    /** Increment the duration of this Note. */
    public void skipBeat(){ this.duration++; }

    /** Produce a Human readable String representation of this Note. */
    public String toString(){
        return ("Note(\"" + this.stringRep() + "\" pitch: " + this.pitch + 
                ", duration: " + this.duration + ", velocity: " + this.velocity+")");
    }

    /** Return the MIDI String representation of this Note. */
    private String stringRep(){
        return "" + this.noteName + this.octave + this.modifier + this.duration;
    }

    /** Adjust the given pitch to be in [0..128] */
    private static int pitch(int pitch){
        return Math.min(Math.max(pitch, 0), 128);
    }

    /** Adjust the given duration to be in [0..16] */
    private static int duration(int duration){
        return Math.min(Math.max(duration, 0), 16);
    }

    /** Adjust the given velocity to be in [0..100] */
    private static int velocity(int velocity){
        return Math.min(Math.max(velocity, 0), 100);
    }
    
    /** Make sure the given name is a valid Note name. */
    private static char noteName(char name){
        if (name == 'A' || name == 'B' || name == 'C' || name == 'D' ||
            name == 'E' || name == 'F' || name == 'G')
            return name;
        return 'A';
    }

    /** Make sure the given modifier is a valid Note modifier. */
    private static char modifier(char mod){
        if (mod == 'n' || mod == 's' || mod == 'f')
            return mod;
        return 'n';
    }

    /** Make sure the given octave is a valid Note octave. */
    private static int octave(int oct){
        return Math.min(Math.max(oct, 0), 8);
    }

    /** Compute the MIDI pitch of the given note name, octave, and modifier. */
    public static int computePitch(char noteName, int octave, char modifier){
        int base = OFFSETS[noteName - 'A'];
        int note = base + 12 * octave + 9;
        if(modifier == 's')
            return note + 1;
        else if (modifier == 'f')
            return note - 1;
        return note;
    }
    
    /** Convert a given Pitch number to it's Octave */
    private static int pitchToOctave(int pitch){
        if(pitch == 0)return 0;
        return (pitch - 8) / 12;
    }
}