//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#ifndef cwMidiFile_h
#define cwMidiFile_h

namespace cw
{
  namespace midi
  {

    namespace file
    {

      // MIDI file timing:
      // Messages in the MIDI file are time tagged with a delta offset in 'ticks'
      // from the previous message in the same track.
      // 
      // A 'tick' can be converted to microsends as follows:
      //
      // microsecond per tick = micros per quarter note / ticks per quarter note
      // 
      // MpT = MpQN / TpQN
      // 
      // TpQN is given as a constant in the MIDI file header.
      // MpQN is given as the value of the MIDI file tempo message.
      //
      // See seekUSecs() for an example of converting ticks to milliseconds.
      //
      // Notes:
      // As part of the file reading process, the status byte of note-on messages 
      // with velocity=0 are is changed to a note-off message. See _readChannelMsg().
      

      typedef struct
      {
        uint8_t hr;
        uint8_t min;
        uint8_t sec;
        uint8_t frm;
        uint8_t sfr;
      } smpte_t;

      typedef struct
      {
        uint8_t num;
        uint8_t den;
        uint8_t metro;
        uint8_t th2s;
      } timeSig_t;

      typedef struct
      {
        uint8_t key;
        uint8_t scale;
      } keySig_t;

      struct midiTrackMsg_str;
  
      typedef struct
      {
        uint8_t  ch;
        uint8_t  d0;
        uint8_t  d1;
        unsigned durMicros;       // note duration in microseconds (corrected for tempo changes)
        struct trackMsg_str* end; // note-off or pedal-up message
      } chMsg_t;

      enum
      {
        kDropTrkMsgFl = 0x01,
        kReleaseFl    = 0x02,
      };

      typedef struct trackMsg_str
      {
        unsigned             flags;   // see k???TrkMsgFl
        unsigned             uid;     // uid's are unique among all msg's in the file
        unsigned             dtick;   // delta ticks between events on this track (ticks between this event and the previous event on this track)
        unsigned long long   atick;   // global (all tracks interleaved) accumulated ticks
        unsigned long long   amicro;  // global (all tracks interleaved) accumulated microseconds adjusted for tempo changes
        uint8_t              status;  // ch msg's have the channel value removed (it is stored in u.chMsgPtr->ch)
        uint8_t              metaId;  //
        unsigned short       trkIdx;  //  
        unsigned             byteCnt; // length of data pointed to by u.voidPtr (or any other pointer in the union)
        struct trackMsg_str* link;    // link to next record in this track

        union
        {
          uint8_t          bVal;
          unsigned         iVal;
          unsigned short   sVal;
          const char*      text;
          const void*      voidPtr;
          const smpte_t*   smptePtr;
          const timeSig_t* timeSigPtr;
          const keySig_t*  keySigPtr;
          const chMsg_t*   chMsgPtr;
          const uint8_t*   sysExPtr;
        } u;
      } trackMsg_t;

      inline bool isNoteOn(const trackMsg_t* m)     { return midi::isChStatus(m->status) ? midi::isNoteOn( m->status,m->u.chMsgPtr->d1) : false; }
      inline bool isNoteOff(const trackMsg_t* m)    { return midi::isChStatus(m->status) ? midi::isNoteOff(m->status,m->u.chMsgPtr->d1) : false; }

      inline bool isPedalUp(const trackMsg_t* m)    { return midi::isChStatus(m->status) ? midi::isPedalUp(    m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1) : false; }
      inline bool isPedalDown(const trackMsg_t* m)  { return midi::isChStatus(m->status) ? midi::isPedalDown(  m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1) : false; }
    
      inline bool isSustainPedalUp(const trackMsg_t* m)     { return midi::isChStatus(m->status) ? midi::isSustainPedalUp(    m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; }
      inline bool isSustainPedalDown(const trackMsg_t* m)   { return midi::isChStatus(m->status) ? midi::isSustainPedalDown(  m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; }
      
      inline bool isSostenutoPedalUp(const trackMsg_t* m)   { return midi::isChStatus(m->status) ? midi::isSostenutoPedalUp(  m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; }
      inline bool isSostenutoPedalDown(const trackMsg_t* m) { return midi::isChStatus(m->status) ? midi::isSostenutoPedalDown(m->status,m->u.chMsgPtr->d0,m->u.chMsgPtr->d1) : false; }
  
      typedef handle<struct file_str> handle_t;

      // Read a MIDI file.
      rc_t open( handle_t& hRef, const char* fn );

      // Read from a CSV.
      // Columns: "uid","tpQN","bpm","dticks","ch","status","d0","d1"
      // tpQN = ticks per quarter note should be given on the first line. (Defaults to 1260).
      // bpm = beats per minute should be given on the first line. (Defaults to 60).
      rc_t open_csv( handle_t& hRef, const char* csv_fname );
      
      // Create an empty MIDI file object.
      rc_t create( handle_t& hRef, unsigned trkN, unsigned ticksPerQN );

      // Release all resources associated with this MIDI file object
      rc_t close( handle_t& hRef );

      // Write this MIDI file to the specified file.
      rc_t write( handle_t h, const char* fn );

      // Return midi file format id (0,1,2) or kInvalidId if 'h' is invalid.
      unsigned fileType( handle_t h );

      // Returns ticks per quarter note or kInvalidMidiByte if 'h' is
      // invalid or 0 if file uses SMPTE ticks per frame time base.
      unsigned ticksPerQN( handle_t h );

      // The file name used in an earlier call to midiFileOpen() or NULL if this 
      // midi file did not originate from an actual file.
      const char* filename( handle_t h );

      // Returns SMPTE ticks per frame or kInvalidMidiByte if 'h' is
      // invalid or 0 if file uses ticks per quarter note time base.
      uint8_t ticksPerSmpteFrame( handle_t h );

      // Returns SMPTE format or kInvalidMidiByte if 'h' is invalid or 0
      // if file uses ticks per quarter note time base.
      uint8_t smpteFormatId( handle_t h );

      // Return the count of tracks in the file.
      unsigned trackCount( handle_t h );

      // Returns count of records in track 'trackIdx' or kInvalidCnt if 'h' is invalid.
      unsigned trackMsgCount( handle_t h, unsigned trackIdx );

      // Returns base of record chain from track 'trackIdx' or NULL if 'h' is invalid.
      const trackMsg_t* trackMsg( handle_t h, unsigned trackIdx );

      // Returns the total count of records in the midi file and the
      // number in the array returned by msgArray(). 
      // Return kInvalidCnt if 'h' is invalid.
      unsigned msgCount( handle_t h );

      // Returns a pointer to the base of an array of pointers to all records
      // in the file sorted in ascending time order. 
      // Returns NULL if 'h' is invalid.
      const trackMsg_t** msgArray( handle_t h );

      // Set the velocity of a note-on/off msg identified by 'uid'.
      rc_t setVelocity( handle_t h, unsigned uid, uint8_t vel );

  
      // Insert a MIDI message relative to the reference msg identified by 'uid'.
      // If dtick is positive/negative then the new msg is inserted after/before the reference msg.  
      rc_t insertMsg( handle_t h, unsigned uid, int dtick, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 );

      //
      // Insert a new trackMsg_t into the MIDI file on the specified track.
      //
      // Only the following fields need be set in 'msg'.
      //   atick    - used to position the msg in the track
      //   status   - this field is always set (Note that channel information must stripped from the status byte and included in the channel msg data)
      //   metaId   - this field is optional depending on the msg type
      //   byteCnt  - used to allocate storage for the data element in 'trackMsg_t.u'
      //   u        - the message data
      //
      rc_t insertTrackMsg(     handle_t h, unsigned trkIdx, const trackMsg_t* msg );
      rc_t insertTrackChMsg(   handle_t h, unsigned trkIdx, unsigned atick, uint8_t status, uint8_t d0, uint8_t d1 );
      rc_t insertTrackTempoMsg( handle_t h, unsigned trkIdx, unsigned atick, unsigned bpm );
  
      // Return a pointer to the first msg at or after 'usecsOffs' or kInvalidIdx if no
      // msg exists after 'usecsOffs'.  Note that 'usecOffs' is an offset from the beginning
      // of the file.
      // On return *'msgUsecsPtr' is set to the actual time of the msg. 
      // (which will be equal to or greater than 'usecsOffs').
      unsigned seekUsecs( handle_t h, unsigned long long usecsOffs, unsigned* msgUsecsPtr, unsigned* newMicrosPerTickPtr );

      double durSecs( handle_t h );

      // Calculate Note Duration
      enum { kWarningsMfFl=0x01, kDropReattacksMfFl=0x02 };
      void calcNoteDurations( handle_t h, unsigned flags );

      // Set the delay prior to the first non-zero msg.
      void setDelay( handle_t h, unsigned ticks );

      void printMsgs( handle_t h, log::handle_t logH );
      void printTrack( handle_t h, unsigned trkIdx, log::handle_t logH );

      typedef struct
      {
        unsigned           uid;
        unsigned long long amicro;
        unsigned           density; 
      } density_t;

      // Generate the note onset density measure for each note in the MIDI file.
      // Delete the returned memory with a call to mem::release().
      density_t* noteDensity( handle_t h, unsigned* cntRef );


      // Generate a piano-roll plot description file which can be displayed with cmXScore.m
      rc_t genPlotFile( const char* midiFn, const char* outFn );

      rc_t genSvgFile(const char* midiFn, const char* outSvgFn, const char* cssFn, bool standAloneFl, bool panZoomFl );

      rc_t genCsvFile( const char* midiFn, const char* csvFn, bool printWarningsFl=true );

      // Generate a text file report using printMsgs()
      rc_t report( const char* midiFn, log::handle_t logH );

      rc_t report_begin_end( const char* midiFn, unsigned msg_cnt=10, bool pitch_only_fl=true, log::handle_t logH=log::handle_t() );

      void printControlNumbers( const char* midiFileName );

      rc_t test( const object_t* cfg );
      
      
      
    }
  }
}


#endif
