#ifndef _MIDIEVENT_H
#define _MIDIEVENT_H

#ifdef __cplusplus
extern "C" {
#endif

enum midi_event_type_t { 
  EV_NOTE_OFF          =0x80, 
  EV_NOTE_ON           =0x90, 
  EV_NOTE_AFTERTOUCH   =0xA0,
  EV_CONTROLLER        =0xB0, 
  EV_PROGRAM_CHANGE    =0xC0,
  EV_CHANNEL_AFTERTOUCH=0xD0, 
  EV_PITCH_BEND        =0xE0,
  EV_SYSEX             =0xF0, // really terminated when last byte is 0xf7 (=='SYSEX_END')
  EV_MTC_QUARTERFRAME  =0xF1, // only on wire
  EV_SONGPOSITION      =0xF2, // only on wire
  EV_SONGSELECT        =0xF3, // only on wire
  EV_TUNEREQUEST       =0xF6, // only on wire
  EV_SYSEX_VERBATIM    =0xF7, // e.g. sysex continuation,  or even midi [-realtime](0xf1-0xff)
  EV_META              =0xFF  // NOTE: channel is usually -1
};

enum midi_meta_type_t {
  META_SEQUENCE_NUMBER=0,
  META_STRING_TEXT,
  META_STRING_COPYRIGHT,
  META_STRING_NAME, // sequence name or track name
  META_STRING_INSTRUMENT, // maybe with META_CHANNELPREFIX
  META_STRING_LYRICS,
  META_STRING_MARKER,
  META_STRING_CUEPOINT=7,
  META_STRING_PROGRAM_NAME,
  META_STRING_DEVICE_NAME,

  META_CHANNELPREFIX=0x20,
  META_PORT, // deprecated by DEVICE_NAME
  META_EOT=0x2F, // END_OF_TRACK

  META_TEMPO=0x51,
  META_SMPTE=0x54,
  META_TIMESIG=0x58,
  META_KEYSIG=0x59,

  META_USER=0x7F, // sequencer specific 

  META_UNKNOWN=0x100
};

enum midi_realtime_t {
  // for playback:
  SYSREAL_CLOCK        =0xF8, // 24 clocks per quarter, or:
  SYSREAL_TICK         =0xF9, // every 10 ms
  SYSREAL_START        =0xFA, // rewind + CONTINUE
  SYSREAL_CONTINUE     =0xFB,
  SYSREAL_STOP         =0xFC,

  SYSREAL_ACTIVE_SENSE =0xFE,
  SYSREAL_RESET        =0xFF
};

#include <stdbool.h>

typedef struct midi_event_t {
  int time; // midi ticks  (relative[our API] or absolute[override by app])  [init val: <0]
  enum midi_event_type_t type;

  char channel;
  union {
    struct {
      enum midi_meta_type_t type;
      union {
        unsigned short seqno;
        struct {
          int len;
          unsigned char *data;
        } string;
        unsigned char chan_prefix;
        unsigned char port;
        struct {
          int usec_per_quarter;
        } tempo;
        struct {
          // subframe=1/100 frame
          enum { FPS_24=0, FPS_25=1, FPS_2997=2, FPS_30=3 } rate;
          unsigned char hour,minute,second,frame,subframe;
        } smpte;
        struct {
          unsigned char num,denoml,metro,t32nd;
        } timesig;
        struct {
          char sharps;
          bool minor;
        } keysig;
        struct {
          int len;
          unsigned char *data;
        } user;
      };
    } meta;
    struct {
      char tune,value;
    } note;
    struct {
      char number,value; // TODO? combine 0-31/32-63 LSB/MSB ?
    } controller;
    struct {
      char number;
    } program;
    struct {
      char value;
    } channel_aftertouch;
    struct {
      signed short value; // -8192...8191
    } pitch_bend;
    struct {
      int len; // not on the wire
      unsigned char *data;
    } sysex;
    unsigned char mtc_byte;
    unsigned short songposition; // in beats = 6 clocks = 16th note
    unsigned char songselect;
  };
} midi_event_t;

static inline bool midi_event_isEOT_fast(midi_event_t *ev) {
  return (ev->type==EV_META)&&(ev->meta.type==META_EOT);
}
static inline bool midi_event_isEOT(midi_event_t *ev) {
  return (ev)&&(ev->time>=0)&&(midi_event_isEOT_fast(ev));
}

void init_midi_event(midi_event_t *ev);
midi_event_t *new_midi_event();
void free_midi_event_data(midi_event_t *ev); // only data
void free_midi_event(midi_event_t *ev);
void clear_midi_event(midi_event_t *ev); // will free data and re-init

void midi_event_dump(const midi_event_t *ev);

// &status is required for running status, init with 0x70

// return consumed bytes  or 0;  will clear ret;  ret.time<0 on error
int midi_event_read_raw(midi_event_t *ret,unsigned char *status,unsigned char *buf,unsigned char *end); // midi on-the-wire, ret.time=0, skips realtime bytes
int midi_event_read(midi_event_t *ret,unsigned char *status,unsigned char *buf,unsigned char *end); // SMF
int midi_event_skip(unsigned char *status,unsigned char *buf,unsigned char *end); // SMF; returns size, -1 on EOT, or 0 on error
// return written bytes  or 0;  allow buf==end==0 for size determination
int midi_event_write_raw(midi_event_t *ev,unsigned char *status,unsigned char *buf,unsigned char *end);
int midi_event_write(midi_event_t *ev,unsigned char *status,unsigned char *buf,unsigned char *end);

#ifdef __cplusplus
}
#endif

#endif
