#region License

/* Copyright (c) 2005 Leslie Sanford
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#endregion

#region Contact

/*
 * Leslie Sanford
 * Email: jabberdabber@hotmail.com
 */

#endregion

using System.ComponentModel;
using System.Diagnostics;

namespace Sanford.Multimedia.Midi
{
    #region Channel Command Types

    /// <summary>
    /// Defines constants for ChannelMessage types.
    /// </summary>
    public enum ChannelCommand 
    {
        /// <summary>
        /// Represents the note-off command type.
        /// </summary>
        NoteOff = 0x80,

        /// <summary>
        /// Represents the note-on command type.
        /// </summary>
        NoteOn = 0x90,

        /// <summary>
        /// Represents the poly pressure (aftertouch) command type.
        /// </summary>
        PolyPressure = 0xA0,

        /// <summary>
        /// Represents the controller command type.
        /// </summary>
        Controller = 0xB0,  
  
        /// <summary>
        /// Represents the program change command type.
        /// </summary>
        ProgramChange = 0xC0,

        /// <summary>
        /// Represents the channel pressure (aftertouch) command 
        /// type.
        /// </summary>
        ChannelPressure = 0xD0,   
     
        /// <summary>
        /// Represents the pitch wheel command type.
        /// </summary>
        PitchWheel = 0xE0
    }

    #endregion

    #region Controller Types

    /// <summary>
    /// Defines constants for controller types.
    /// </summary>
    public enum ControllerType
    {
        /// <summary>
        /// The Bank Select coarse.
        /// </summary>
        BankSelect,

        /// <summary>
        /// The Modulation Wheel coarse.
        /// </summary>
        ModulationWheel,

        /// <summary>
        /// The Breath Control coarse.
        /// </summary>
        BreathControl,

        /// <summary>
        /// The Foot Pedal coarse.
        /// </summary>
        FootPedal = 4,

        /// <summary>
        /// The Portamento Time coarse.
        /// </summary>
        PortamentoTime,

        /// <summary>
        /// The Data Entry Slider coarse.
        /// </summary>
        DataEntrySlider,

        /// <summary>
        /// The Volume coarse.
        /// </summary>
        Volume,

        /// <summary>
        /// The Balance coarse.
        /// </summary>
        Balance,

        /// <summary>
        /// The Pan position coarse.
        /// </summary>
        Pan = 10,

        /// <summary>
        /// The Expression coarse.
        /// </summary>
        Expression,

        /// <summary>
        /// The Effect Control 1 coarse.
        /// </summary>
        EffectControl1,

        /// <summary>
        /// The Effect Control 2 coarse.
        /// </summary>
        EffectControl2,

        /// <summary>
        /// The General Puprose Slider 1
        /// </summary>
        GeneralPurposeSlider1 = 16,

        /// <summary>
        /// The General Puprose Slider 2
        /// </summary>
        GeneralPurposeSlider2,

        /// <summary>
        /// The General Puprose Slider 3
        /// </summary>
        GeneralPurposeSlider3,

        /// <summary>
        /// The General Puprose Slider 4
        /// </summary>
        GeneralPurposeSlider4,

        /// <summary>
        /// The Bank Select fine.
        /// </summary>
        BankSelectFine = 32,

        /// <summary>
        /// The Modulation Wheel fine.
        /// </summary>
        ModulationWheelFine,

        /// <summary>
        /// The Breath Control fine.
        /// </summary>
        BreathControlFine,

        /// <summary>
        /// The Foot Pedal fine.
        /// </summary>
        FootPedalFine = 36,

        /// <summary>
        /// The Portamento Time fine.
        /// </summary>
        PortamentoTimeFine,

        /// <summary>
        /// The Data Entry Slider fine.
        /// </summary>
        DataEntrySliderFine,

        /// <summary>
        /// The Volume fine.
        /// </summary>
        VolumeFine,

        /// <summary>
        /// The Balance fine.
        /// </summary>
        BalanceFine,

        /// <summary>
        /// The Pan position fine.
        /// </summary>
        PanFine = 42,

        /// <summary>
        /// The Expression fine.
        /// </summary>
        ExpressionFine,

        /// <summary>
        /// The Effect Control 1 fine.
        /// </summary>
        EffectControl1Fine,

        /// <summary>
        /// The Effect Control 2 fine.
        /// </summary>
        EffectControl2Fine,

        /// <summary>
        /// The Hold Pedal 1.
        /// </summary>
        HoldPedal1 = 64,

        /// <summary>
        /// The Portamento.
        /// </summary>
        Portamento,

        /// <summary>
        /// The Sustenuto Pedal.
        /// </summary>
        SustenutoPedal,

        /// <summary>
        /// The Soft Pedal.
        /// </summary>
        SoftPedal,

        /// <summary>
        /// The Legato Pedal.
        /// </summary>
        LegatoPedal,

        /// <summary>
        /// The Hold Pedal 2.
        /// </summary>
        HoldPedal2,

        /// <summary>
        /// The Sound Variation.
        /// </summary>
        SoundVariation,

        /// <summary>
        /// The Sound Timbre.
        /// </summary>
        SoundTimbre,

        /// <summary>
        /// The Sound Release Time.
        /// </summary>
        SoundReleaseTime,

        /// <summary>
        /// The Sound Attack Time.
        /// </summary>
        SoundAttackTime,

        /// <summary>
        /// The Sound Brightness.
        /// </summary>
        SoundBrightness,

        /// <summary>
        /// The Sound Control 6.
        /// </summary>
        SoundControl6,

        /// <summary>
        /// The Sound Control 7.
        /// </summary>
        SoundControl7,

        /// <summary>
        /// The Sound Control 8.
        /// </summary>
        SoundControl8,

        /// <summary>
        /// The Sound Control 9.
        /// </summary>
        SoundControl9,

        /// <summary>
        /// The Sound Control 10.
        /// </summary>
        SoundControl10,

        /// <summary>
        /// The General Purpose Button 1.
        /// </summary>
        GeneralPurposeButton1,

        /// <summary>
        /// The General Purpose Button 2.
        /// </summary>
        GeneralPurposeButton2,

        /// <summary>
        /// The General Purpose Button 3.
        /// </summary>
        GeneralPurposeButton3,

        /// <summary>
        /// The General Purpose Button 4.
        /// </summary>
        GeneralPurposeButton4,

        /// <summary>
        /// The Effects Level.
        /// </summary>
        EffectsLevel = 91,

        /// <summary>
        /// The Tremelo Level.
        /// </summary>
        TremeloLevel,
        
        /// <summary>
        /// The Chorus Level.
        /// </summary>
        ChorusLevel,

        /// <summary>
        /// The Celeste Level.
        /// </summary>
        CelesteLevel,

        /// <summary>
        /// The Phaser Level.
        /// </summary>
        PhaserLevel,

        /// <summary>
        /// The Data Button Increment.
        /// </summary>
        DataButtonIncrement,

        /// <summary>
        /// The Data Button Decrement.
        /// </summary>
        DataButtonDecrement,

        /// <summary>
        /// The NonRegistered Parameter Fine.
        /// </summary>
        NonRegisteredParameterFine,

        /// <summary>
        /// The NonRegistered Parameter Coarse.
        /// </summary>
        NonRegisteredParameterCoarse,

        /// <summary>
        /// The Registered Parameter Fine.
        /// </summary>
        RegisteredParameterFine,

        /// <summary>
        /// The Registered Parameter Coarse.
        /// </summary>
        RegisteredParameterCoarse,

        /// <summary>
        /// The All Sound Off.
        /// </summary>
        AllSoundOff = 120,

        /// <summary>
        /// The All Controllers Off.
        /// </summary>
        AllControllersOff,

        /// <summary>
        /// The Local Keyboard.
        /// </summary>
        LocalKeyboard,
        
        /// <summary>
        /// The All Notes Off.
        /// </summary>
        AllNotesOff,

        /// <summary>
        /// The Omni Mode Off.
        /// </summary>
        OmniModeOff,

        /// <summary>
        /// The Omni Mode On.
        /// </summary>
        OmniModeOn,

        /// <summary>
        /// The Mono Operation.
        /// </summary>
        MonoOperation,

        /// <summary>
        /// The Poly Operation.
        /// </summary>
        PolyOperation
    }

    #endregion

    /// <summary>
    /// Represents MIDI channel messages.
    /// </summary>
    [ImmutableObject(true)]
    public sealed class ChannelMessage : ShortMessage
    {
        #region ChannelEventArgs Members

        #region Constants

        //
        // Bit manipulation constants.
        //

        private const int MidiChannelMask = ~15;
        private const int CommandMask = ~240;

        /// <summary>
        /// Maximum value allowed for MIDI channels.
        /// </summary> 
        public const int MidiChannelMaxValue = 15;

        #endregion

        #region Construction
        
        /// <summary>
        /// Initializes a new instance of the ChannelEventArgs class with the
        /// specified command, MIDI channel, and data 1 values.
        /// </summary>
        /// <param name="command">
        /// The command value.
        /// </param>
        /// <param name="midiChannel">
        /// The MIDI channel.
        /// </param>
        /// <param name="data1">
        /// The data 1 value.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If midiChannel is less than zero or greater than 15. Or if 
        /// data1 is less than zero or greater than 127.
        /// </exception>
        public ChannelMessage(ChannelCommand command, int midiChannel, int data1)
        { 
            msg = 0;

            msg = PackCommand(msg, command);
            msg = PackMidiChannel(msg, midiChannel);
            msg = PackData1(msg, data1);

            #region Ensure

            Debug.Assert(Command == command);
            Debug.Assert(MidiChannel == midiChannel);
            Debug.Assert(Data1 == data1);

            #endregion
        }        

        /// <summary>
        /// Initializes a new instance of the ChannelEventArgs class with the 
        /// specified command, MIDI channel, data 1, and data 2 values.
        /// </summary>
        /// <param name="command">
        /// The command value.
        /// </param>
        /// <param name="midiChannel">
        /// The MIDI channel.
        /// </param>
        /// <param name="data1">
        /// The data 1 value.
        /// </param>
        /// <param name="data2">
        /// The data 2 value.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If midiChannel is less than zero or greater than 15. Or if 
        /// data1 or data 2 is less than zero or greater than 127. 
        /// </exception>
        public ChannelMessage(ChannelCommand command, int midiChannel, 
            int data1, int data2)
        {
            msg = 0;

            msg = PackCommand(msg, command);
            msg = PackMidiChannel(msg, midiChannel);
            msg = PackData1(msg, data1);
            msg = PackData2(msg, data2);

            #region Ensure

            Debug.Assert(Command == command);
            Debug.Assert(MidiChannel == midiChannel);
            Debug.Assert(Data1 == data1);
            Debug.Assert(Data2 == data2);

            #endregion
        }

        internal ChannelMessage(int message)
        {
            msg = message;            
        }

        #endregion

        #region Methods

        /// <summary>
        /// Returns a value for the current ChannelEventArgs suitable for use in 
        /// hashing algorithms.
        /// </summary>
        /// <returns>
        /// A hash code for the current ChannelEventArgs.
        /// </returns>
        public override int GetHashCode()
        {
            return msg;
        }

        /// <summary>
        /// Determines whether two ChannelEventArgs instances are equal.
        /// </summary>
        /// <param name="obj">
        /// The ChannelMessageEventArgs to compare with the current ChannelEventArgs.
        /// </param>
        /// <returns>
        /// <b>true</b> if the specified object is equal to the current 
        /// ChannelMessageEventArgs; otherwise, <b>false</b>.
        /// </returns>
        public override bool Equals(object obj)
        {
            #region Guard

            if(!(obj is ChannelMessage))
            {
                return false;
            }

            #endregion
            
            ChannelMessage e = (ChannelMessage)obj;            

            return msg == e.msg;
        }

        /// <summary>
        /// Returns a value indicating how many bytes are used for the 
        /// specified ChannelCommand.
        /// </summary>
        /// <param name="command">
        /// The ChannelCommand value to test.
        /// </param>
        /// <returns>
        /// The number of bytes used for the specified ChannelCommand.
        /// </returns>
        internal static int DataBytesPerType(ChannelCommand command)
        {
            int result;

            if(command == ChannelCommand.ChannelPressure ||
               command == ChannelCommand.ProgramChange)
            {
                result = 1;
            }
            else
            {
                result = 2;
            }

            return result;
        }
   
        /// <summary>
        /// Unpacks the command value from the specified integer channel 
        /// message.
        /// </summary>
        /// <param name="message">
        /// The message to unpack.
        /// </param>
        /// <returns>
        /// The command value for the packed message.
        /// </returns>
        internal static ChannelCommand UnpackCommand(int message)
        {
            return (ChannelCommand)(message & DataMask & MidiChannelMask);
        }
     
        /// <summary>
        /// Unpacks the MIDI channel from the specified integer channel 
        /// message.
        /// </summary>
        /// <param name="message">
        /// The message to unpack.
        /// </param>
        /// <returns>
        /// The MIDI channel for the pack message.
        /// </returns>
        internal static int UnpackMidiChannel(int message)
        {
            return message & DataMask & CommandMask;
        }

        /// <summary>
        /// Packs the MIDI channel into the specified integer message.
        /// </summary>
        /// <param name="message">
        /// The message into which the MIDI channel is packed.
        /// </param>
        /// <param name="midiChannel">
        /// The MIDI channel to pack into the message.
        /// </param>
        /// <returns>
        /// An integer message.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If midiChannel is less than zero or greater than 15.
        /// </exception>
        internal static int PackMidiChannel(int message, int midiChannel)
        {
            #region Preconditons

            if(midiChannel < 0 || midiChannel > MidiChannelMaxValue)
            {
                throw new ArgumentOutOfRangeException("midiChannel", midiChannel,
                    "MIDI channel out of range.");
            }

            #endregion

            return (message & MidiChannelMask) | midiChannel;
        }

        /// <summary>
        /// Packs the command value into an integer message.
        /// </summary>
        /// <param name="message">
        /// The message into which the command is packed.
        /// </param>
        /// <param name="command">
        /// The command value to pack into the message.
        /// </param>
        /// <returns>
        /// An integer message.
        /// </returns>
        internal static int PackCommand(int message, ChannelCommand command)
        {
            return (message & CommandMask) | (int)command;
        }        

        #endregion

        #region Properties
        
        /// <summary>
        /// Gets the channel command value.
        /// </summary>
        public ChannelCommand Command
        {
            get
            {
                return UnpackCommand(msg);
            }
        }
        
        /// <summary>
        /// Gets the MIDI channel.
        /// </summary>
        public int MidiChannel
        {
            get
            {
                return UnpackMidiChannel(msg);
            }
        }

        /// <summary>
        /// Gets the first data value.
        /// </summary>
        public int Data1
        {
            get
            {
                return UnpackData1(msg);
            }                
        }
        
        /// <summary>
        /// Gets the second data value.
        /// </summary>
        public int Data2
        {
            get
            {
                return UnpackData2(msg);
            }
        }

        /// <summary>
        /// Gets the EventType.
        /// </summary>
        public override MessageType MessageType
        {
            get
            {
                return MessageType.Channel;
            }
        }

        #endregion

        #endregion        
    }
}