#ifndef _FMOD_CODEC_MIDI_H
#define _FMOD_CODEC_MIDI_H

#include "fmod_settings.h"

#ifdef FMOD_SUPPORT_MIDI

#include "fmod_music.h"

namespace FMOD
{
    class DSPI;
    class ChannelPool;
    class CodecMIDI;
    class CodecMIDITrack;
    class CodecMIDIChannel;

    #ifdef FMOD_SUPPORT_PRAGMAPACK
	    #pragma pack(1)
    #endif

    typedef struct 
    {
       char           mID[4]   FMOD_PACKED_INTERNAL;
       unsigned int   mSize    FMOD_PACKED_INTERNAL; 
    } FMOD_PACKED MIDI_CHUNK;

    typedef struct
    {
       MIDI_CHUNK     mChunk      FMOD_PACKED_INTERNAL;
       unsigned short mFormat     FMOD_PACKED_INTERNAL;
       unsigned short mNumTracks  FMOD_PACKED_INTERNAL;
       unsigned short mDivision   FMOD_PACKED_INTERNAL;
    } FMOD_PACKED MTHD_CHUNK;

    #ifdef FMOD_SUPPORT_PRAGMAPACK
        #ifdef 	CODEWARRIOR
        #pragma pack(0)
        #else
        #pragma pack()
        #endif
    #endif

    typedef enum
    {
        MIDI_FORMAT_SINGLETRACK,
        MIDI_FORMAT_MULTITRACK,
        MIDI_FORMAT_PATTERN,
    } MIDI_FORMAT;

    static const unsigned char MIDI_VOICE_NOTEOFF           = 0x80;
    static const unsigned char MIDI_VOICE_NOTEON            = 0x90;
    static const unsigned char MIDI_VOICE_AFTERTOUCH        = 0xA0;
    static const unsigned char MIDI_VOICE_CONTROLLERCHANGE  = 0xB0;
    static const unsigned char MIDI_VOICE_PROGRAMCHANGE     = 0xC0;
    static const unsigned char MIDI_VOICE_CHANNELKEYPRESSURE= 0xD0;
    static const unsigned char MIDI_VOICE_PITCHBEND         = 0xE0;
    static const unsigned char MIDI_SYSEXESCAPE             = 0xF7;
    static const unsigned char MIDI_SYSEXEVENT              = 0xF0;
    static const unsigned char MIDI_METAEVENT               = 0xFF;

    static const unsigned char MIDI_METAEVENT_SEQNUMBER     = 0x00;
    static const unsigned char MIDI_METAEVENT_TEXT          = 0x01;
    static const unsigned char MIDI_METAEVENT_COPYRIGHT     = 0x02;
    static const unsigned char MIDI_METAEVENT_NAME          = 0x03;
    static const unsigned char MIDI_METAEVENT_INSTRUMENT    = 0x04;
    static const unsigned char MIDI_METAEVENT_LYRIC         = 0x05;
    static const unsigned char MIDI_METAEVENT_MARKER        = 0x06;
    static const unsigned char MIDI_METAEVENT_CUEPOINT      = 0x07;
    static const unsigned char MIDI_METAEVENT_PATCHNAME     = 0x08;
    static const unsigned char MIDI_METAEVENT_PORTNAME      = 0x09;
    static const unsigned char MIDI_METAEVENT_CHANNEL       = 0x20;
    static const unsigned char MIDI_METAEVENT_PORT          = 0x21;
    static const unsigned char MIDI_METAEVENT_ENDOFTRACK    = 0x2F;
    static const unsigned char MIDI_METAEVENT_TEMPO         = 0x51;
    static const unsigned char MIDI_METAEVENT_SMTPOFFSET    = 0x54;
    static const unsigned char MIDI_METAEVENT_TIMESIGNATURE = 0x58;
    static const unsigned char MIDI_METAEVENT_KEYSIGNATURE  = 0x59;
    static const unsigned char MIDI_METAEVENT_PROPRIETARY   = 0x7F;

    static const unsigned char MIDI_CONTROLLERCHANGE_BANKSELECT_MSB                =  0; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_MODULATIONWHEEL_MSB           =  1; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_BREATHCONTROL_MSB             =  2; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_FOOTCONTROLLER_MSB            =  4; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_PORTAMENTOTIME_MSB            =  5; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_DATAENTRY_MSB                 =  6; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_CHANNELVOLUME_MSB             =  7; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_BALANCE_MSB                   =  8; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_PAN_MSB                       = 10; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EXPRESSIONCONTROLLER_MSB      = 11; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTCONTROL1_MSB            = 12; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTCONTROL2_MSB            = 13; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER1_MSB = 16; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER2_MSB = 17; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER3_MSB = 18; // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER4_MSB = 19; // 0-127 MSB 

    static const unsigned char MIDI_CONTROLLERCHANGE_BANKSELECT_LSB                = 32; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_MODULATIONWHEEL_LSB           = 33; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_BREATHCONTROL_LSB             = 34; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_FOOTCONTROLLER_LSB            = 36; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_PORTAMENTOTIME_LSB            = 37; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_DATAENTRY_LSB                 = 38; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_CHANNELVOLUME_LSB             = 39; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_BALANCE_LSB                   = 40; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_PAN_LSB                       = 42; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EXPRESSIONCONTROLLER_LSB      = 43; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTCONTROL1_LSB            = 44; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTCONTROL2_LSB            = 45; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER1_LSB = 48; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER2_LSB = 49; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER3_LSB = 50; // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER4_LSB = 51; // 0-127 LSB 

    static const unsigned char MIDI_CONTROLLERCHANGE_DAMPERPEDALONOFF              = 64;   // >64=ON 
    static const unsigned char MIDI_CONTROLLERCHANGE_PORTAMENTOONOFF               = 65;   // >64=ON 
    static const unsigned char MIDI_CONTROLLERCHANGE_SUSTENUTOONOFF                = 66;   // >64=ON 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOFTPEDALONOFF                = 67;   // >64=ON 
    static const unsigned char MIDI_CONTROLLERCHANGE_LEGATOFOOTSWITCH              = 68;   // >64=ON 
    static const unsigned char MIDI_CONTROLLERCHANGE_HOLD2                         = 69;   // >64=ON 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER1              = 70;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER2              = 71;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER3              = 72;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER4              = 73;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER5              = 74;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER6              = 75;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER7              = 76;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER8              = 77;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER9              = 78;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_SOUNDCONTROLLER10             = 79;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER5     = 80;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER6     = 81;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER7     = 82;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_GENERALPURPOSECONTROLLER8     = 83;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_PORTAMENTOCONTROL             = 84;   // -127 SOURCE NOTE 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTS1DEPTH                 = 91;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTS2DEPTH                 = 92;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTS3DEPTH                 = 93;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTS4DEPTH                 = 94;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_EFFECTS5DEPTH                 = 95;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_DATAENTRYINC                  = 96;   // N/A   
    static const unsigned char MIDI_CONTROLLERCHANGE_DATAENTRYDEC                  = 97;   // N/A   
    static const unsigned char MIDI_CONTROLLERCHANGE_NONREGISTEREDPARAMNUMBER_LSB  = 98;   // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_NONREGISTEREDPARAMNUMBER_MSB  = 99;   // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_REGISTEREDPARAMETERNUMBER_LSB = 100;  // 0-127 LSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_REGISTEREDPARAMETERNUMBER_MSB = 101;  // 0-127 MSB 
    static const unsigned char MIDI_CONTROLLERCHANGE_ALLSOUNDOFF                   = 120;
    static const unsigned char MIDI_CONTROLLERCHANGE_RESETALLCONTROLLERS           = 121;
    static const unsigned char MIDI_CONTROLLERCHANGE_LOCALCONTROLONOFF             = 122;   
    static const unsigned char MIDI_CONTROLLERCHANGE_ALLNOTESOFF                   = 123;  
    static const unsigned char MIDI_CONTROLLERCHANGE_OMNIMODEOFF                   = 124; 
    static const unsigned char MIDI_CONTROLLERCHANGE_OMNIMODEON                    = 125;
    static const unsigned char MIDI_CONTROLLERCHANGE_POLYMODEONOFF                 = 126;
    static const unsigned char MIDI_CONTROLLERCHANGE_POLYMODEON                    = 127;

    static const unsigned char MIDI_CONTROLLERCHANGE_RPN_PITCHBENDSENSITIVITY      = 0;

    static const unsigned int MIDI_F_INSTRUMENT_DRUMS = 0x80000000;

    static const int MIDI_DEFAULTSUBCHANNELS        = 32;
    static const int MIDI_MINIMUMSAMPLEGRANULARITY  = 512;      // 10.66ms @ 48khz :S

    class CodecMIDIDLSCache : public LinkedListNode
    {
      public:

        char    mFilePath[FMOD_STRING_MAXPATHLEN];
        SoundI *mDLSFile;
        int     mRefCount;
    };

    class CodecMIDISubChannel : public LinkedListNode
    {
      public:

        CodecMIDI           *mMIDI;
        ChannelI             mChannel;
        CodecDLSInstrument  *mInstrument;
                            
        float                mLFOStartDelay;
        float                mLFOTime;
        float                mLFOFrequency;
        CodecDLSEnvelope     mVolumeEnvelope;
        CodecDLSEnvelope     mPitchEnvelope;
                            
        int                  mCurrentNote;       
        SoundI              *mSound;
        unsigned char        mKeyOnKey;
        unsigned char        mUnityNote;
        int                  mFineTune;
        unsigned char        mKeyOnVelocity;
        int                  mNumArticulators;
        DLS_CONNECTIONBLOCK *mArticulator;
                            
        float                mTremoloScale;
        float                mVibratoScale;
        float                mPanbrelloScale;
                            
        bool                 mKeyOff;
        int                  mMiddleC;
        int                  mKeyGroup;
        float                mSampleAttenuation;
        CodecMIDIChannel    *mParent;
                            
        FMOD_RESULT          displayArticulators();
        FMOD_RESULT          findArticulator(int src, int dest);
        FMOD_RESULT          articulateDest(CONN_SRC_FLAGS srcflags, int dest, int *usDestination);
        FMOD_RESULT          setUpArticulators();
        FMOD_RESULT          stop();
        FMOD_RESULT          updateVolume();
        FMOD_RESULT          updatePitch();
        FMOD_RESULT          updatePan();

        float                getTimeCentsFromlScale(int lScale);
    };

    class CodecMIDIChannel : public LinkedListNode
    {
      public:
        CodecMIDITrack     *mTrack;
        CodecMIDISubChannel mChannelHead;
        unsigned char       mIndex;
        unsigned char       mKeyOffVelocity;
        unsigned char       mKeyOnVelocity;
        unsigned char       mAfterTouchKey;
        unsigned char       mAfterTouchPressure;
        unsigned int        mBank;
        unsigned char       mProgram;
        unsigned int        mRPN;
        unsigned char       mChannelPressure;
        float               mMasterVolume;
        int                 mPitchBend;
        int                 mPitchBendSensitivity;
        bool                mDamperPedal;

        int                 mModWheel;          // cc1
        int                 mVolume;            // cc7
        int                 mPan;               // cc10
        int                 mExpression;        // cc11
        
        FMOD_RESULT         getSound(int key, SoundI **sound, CodecDLSInstrument **instrument, int *unitynote, int *finetune, int *attenuation, bool *duplicateallowed, int *keygroup, int *numarticulators, DLS_CONNECTIONBLOCK **articulators);
        FMOD_RESULT         process(unsigned char event, bool reuse, unsigned char runningdata, bool calledfromopen);
        FMOD_RESULT         update();
    };

    class CodecMIDITrack
    {
      public:
        
        CodecMIDI          *mMIDI;

        unsigned char      *mData;
        unsigned int        mOffset;
        unsigned int        mLength;
        int                 mIndex;
        bool                mReadDelta;
        float               mTick;
        bool                mFinished;
        unsigned char       mPort;
        unsigned char       mEvent;
                                                                                  
        FMOD_RESULT         readVarLen(unsigned int *result);
        FMOD_RESULT         readByte(unsigned char *result);
        FMOD_RESULT         readWord(unsigned short *result);
        FMOD_RESULT         readDWord(unsigned int *result);
        FMOD_RESULT         read(void *buff, int length);
        FMOD_RESULT         addTag(const char *name, int length, bool calledfromopen);
        FMOD_RESULT         process(bool calledfromopen);
    };


    class CodecMIDI : public Codec
    {
        friend class CodecMIDIChannel;
        friend class CodecMIDITrack;

      private:

        DSPI                *mDSPHead;
        ChannelPool         *mChannelPool;
        ChannelSoftware     *mChannelSoftware;      /* Array of FMOD Ex low level real channels.  Each one of these lives in a ChannelI channel.  */
        int                  mNumSubChannels;
        ChannelGroupI        mChannelGroup;
        CodecMIDISubChannel *mMIDISubChannel;
        CodecMIDIChannel     mMIDIChannel[16];
                           
        bool                mFinished;
                           
        int                 mSequenceNumber;
        unsigned int        mMixerSamplesLeft;
        unsigned int        mMixerSamplesPerTick;
        float               mTimingScale;
        unsigned int        mPCMOffset;
        unsigned int        mDSPTick;
                           
        int                 mNumTracks;
        MIDI_FORMAT         mMIDIFormat;
        int                 mDivision;
        unsigned char       mSMTPOffsetHours;
        unsigned char       mSMTPOffsetMinutes;
        unsigned char       mSMTPOffsetSeconds;
        unsigned char       mSMTPOffsetFrames;
        unsigned char       mSMTPOffsetSubFrames;
                           
        unsigned char       mTimeSignatureNumerator;
        unsigned char       mTimeSignatureDenominator;
        unsigned char       mTimeSignatureMetronome;
        unsigned char       mTimeSignatureNotated32nds;
                           
        unsigned char       mKeySignatureSF;
        unsigned char       mKeySignatureMI;

        CodecMIDITrack     *mTrack;
        unsigned int        mTempo;
        float               mTick;
        float               mMillisecondsPerTick;
        float               mMillisecondsPlayed;    /* Only valid while loading */
                           
        CodecMIDIDLSCache  *mDLSCache;
        SoundI             *mDLSSound;
        CodecDLS           *mDLS;
        bool               *mSampleInclusionList;
                           
        FMOD_RESULT         play(bool fromopen);
        FMOD_RESULT         readVarLen(unsigned int *length);      
        FMOD_RESULT         update(bool audible);
                           
        FMOD_RESULT         openInternal(FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo);
        FMOD_RESULT         closeInternal();
        FMOD_RESULT         readInternal(void *buffer, unsigned int size, unsigned int *read);
        FMOD_RESULT         setPositionInternal(int subsound, unsigned int position, FMOD_TIMEUNIT postype);
        FMOD_RESULT         getMusicNumChannelsInternal(int *numchannels);
        FMOD_RESULT         setMusicChannelVolumeInternal(int channel, float volume);
        FMOD_RESULT         getMusicChannelVolumeInternal(int channel, float *volume);

        static CodecMIDIDLSCache gDLSCacheHead;
        
      public:

        CodecMIDISubChannel mChannelFreeListHead;

        static FMOD_RESULT F_CALLBACK openCallback(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo);
        static FMOD_RESULT F_CALLBACK closeCallback(FMOD_CODEC_STATE *codec);
        static FMOD_RESULT F_CALLBACK readCallback(FMOD_CODEC_STATE *codec, void *buffer, unsigned int sizebytes, unsigned int *bytesread);
        static FMOD_RESULT F_CALLBACK setPositionCallback(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype);
        static FMOD_RESULT F_CALLBACK getMusicNumChannelsCallback(FMOD_CODEC_STATE *codec, int *numchannels);
        static FMOD_RESULT F_CALLBACK setMusicChannelVolumeCallback(FMOD_CODEC_STATE *codec, int channel, float volume);
        static FMOD_RESULT F_CALLBACK getMusicChannelVolumeCallback(FMOD_CODEC_STATE *codec, int channel, float *volume);

        static FMOD_CODEC_DESCRIPTION_EX *getDescriptionEx();  
        
    };
}

#endif  /* FMOD_SUPPORT_MIDI */

#endif

