#include "midievent.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "midifile.h"

void init_midi_event(midi_event_t *ev) // {{{
{
  assert(ev);
  if (ev) {
    ev->time=-1;
    ev->channel=-1;
  }
}
// }}}

midi_event_t *new_midi_event() // {{{
{
  midi_event_t *ev=malloc(sizeof(midi_event_t));
  init_midi_event(ev);
  return ev;
}
// }}}

void free_midi_event_data(midi_event_t *ev) // {{{
{
  assert(ev);
  if ( (ev)&&(ev->time>=0) ) {
    if ( (ev->type==EV_SYSEX)||(ev->type==EV_SYSEX_VERBATIM) ) {
      free(ev->sysex.data);
      ev->sysex.data=0;
    } else if (ev->type==EV_META) {
      if ( (ev->meta.type>=META_STRING_TEXT)&&
           (ev->meta.type<=META_STRING_DEVICE_NAME) ) {
        free(ev->meta.string.data);
        ev->meta.string.data=0;
      } else if ( (ev->meta.type==META_USER)||
                  (ev->meta.type&META_UNKNOWN) ) {
        free(ev->meta.user.data);
        ev->meta.user.data=0;
      }
    }
  }
}
// }}}

void free_midi_event(midi_event_t *ev) // {{{
{
  assert(ev);
  free_midi_event_data(ev);
  free(ev);
}
// }}}

void clear_midi_event(midi_event_t *ev) // {{{
{
  free_midi_event_data(ev);
  init_midi_event(ev);
}
// }}}

#include <ctype.h>
// external linkage, might be useful ...
void midi_hexdump(unsigned char *buf,int len) // {{{
{
  if (!buf) {
    fprintf(stderr,"nil");
    return;
  }
  int iA,iB;
  for (iA=0;iA<len;) {
    for (iB=0;(iB<16)&&(iA<len);iA++,iB++) {
      fprintf(stderr,"%02x ",buf[iA]);
    }
    iA-=iB;
    for (;iB<17;iB++) {
      fputs("   ",stderr);
    }
    for (iB=0;(iB<16)&&(iA<len);iA++,iB++) {
      if (isgraph(buf[iA])) {
        fputc(buf[iA],stderr);
      } else {
        fputc('.',stderr);
      }
    }
    fputc('\n',stderr);
  }
}
// }}}

// TODO ... UNKNOWN/USER
static void dump_meta(const midi_event_t *ev) // {{{
{
  static const char *ms_name[]={NULL,"TEXT","COPYRIGHT","NAME","INSTRUMENT","LYRICS","MARKER","CUEPOINT","PROGRAM_NAME","DEVICE_NAME"};
  static const char *fps_name[]={"24","25","29.97","30"};
  assert( (ev)&&(ev->type==EV_META) );
  switch (ev->meta.type) {
  case META_SEQUENCE_NUMBER:
    fprintf(stderr,"[M sequence number: %d]",ev->meta.seqno);
    break;
  case META_STRING_TEXT:
  case META_STRING_COPYRIGHT:
  case META_STRING_NAME:
  case META_STRING_LYRICS:
  case META_STRING_MARKER:
  case META_STRING_CUEPOINT:
  case META_STRING_PROGRAM_NAME:
  case META_STRING_DEVICE_NAME:
    fprintf(stderr,"[M %s(%d): %.*s]",ms_name[ev->meta.type],ev->meta.string.len,ev->meta.string.len,ev->meta.string.data);
    break;
  case META_STRING_INSTRUMENT: // maybe with META_CHANNELPREFIX
    fprintf(stderr,"[M %s(%d),ch %d: %.*s]",ms_name[ev->meta.type],ev->meta.string.len,ev->channel,ev->meta.string.len,ev->meta.string.data);
    break;
  case META_CHANNELPREFIX:
    fprintf(stderr,"[M channel prefix: %d]",ev->meta.chan_prefix);
    break;
  case META_PORT:
    fprintf(stderr,"[M port: %d]",ev->meta.port);
    break;
  case META_EOT:
    fprintf(stderr,"[M EOT]");
    break;
  case META_TEMPO:
    fprintf(stderr,"[M TEMPO: %d usec per quarter = %.1f BPM]",
      ev->meta.tempo.usec_per_quarter,60000000./ev->meta.tempo.usec_per_quarter);
    break;
  case META_SMPTE:
    fprintf(stderr,"[M SMPTE: %s fps(%d), %d:%d:%d:%d.%d]",
      ((ev->meta.smpte.rate<0)||(ev->meta.smpte.rate>=4)?"?":fps_name[ev->meta.smpte.rate]),
      ev->meta.smpte.rate,
      ev->meta.smpte.hour,
      ev->meta.smpte.minute,
      ev->meta.smpte.second,
      ev->meta.smpte.frame,
      ev->meta.smpte.subframe);
    break;
  case META_TIMESIG:
    fprintf(stderr,"[M TIMESIG: %d/%d, metro: %d (%.2f quarters/click), 32nd/quarter: %d]",
      ev->meta.timesig.num,(1<<ev->meta.timesig.denoml),
      ev->meta.timesig.metro,ev->meta.timesig.metro/24.0,
      ev->meta.timesig.t32nd);
    break;
  case META_KEYSIG:
    fprintf(stderr,"[M KEYSIG: %d sharps, %s]",
      ev->meta.keysig.sharps,((ev->meta.keysig.minor)?"minor":"major"));
    break;

  case META_USER:
// TODO;  META_UNKNOWN=0x100
  default:
    fprintf(stderr,"[Unknown meta: %d]",ev->meta.type);
  }
}
// }}}

void midi_event_dump(const midi_event_t *ev) // {{{
{
  if (!ev) {
    fprintf(stderr,"[null]");
    return;
  } else if (ev->time<0) {
    fprintf(stderr,"[invalid]");
    return;
  }
  fprintf(stderr,"@%d",ev->time);
  switch (ev->type) {
  case EV_NOTE_OFF:
    fprintf(stderr,"[Off,ch %d,key %d,vel %d]",ev->channel,ev->note.tune,ev->note.value);
    break;
  case EV_NOTE_ON:
    fprintf(stderr,"[On,ch %d,key %d,vel %d]",ev->channel,ev->note.tune,ev->note.value);
    break;
  case EV_NOTE_AFTERTOUCH:
    fprintf(stderr,"[Atouch,ch %d,key %d,val %d]",ev->channel,ev->note.tune,ev->note.value);
    break;
  case EV_CONTROLLER:
    fprintf(stderr,"[Controller,ch %d,num %d,val %d]",ev->channel,ev->controller.number,ev->controller.value);
    break;
  case EV_PROGRAM_CHANGE:
    fprintf(stderr,"[PrgChange,ch %d: %d]",ev->channel,ev->program.number);
    break;
  case EV_CHANNEL_AFTERTOUCH:
    fprintf(stderr,"[Atouch,ch %d,val %d]",ev->channel,ev->channel_aftertouch.value);
    break;
  case EV_PITCH_BEND:
    fprintf(stderr,"[Pitch,ch %d: %d]",ev->channel,ev->pitch_bend.value);
    break;
  case EV_SYSEX:
  case EV_SYSEX_VERBATIM:
    fprintf(stderr,"[%s,len %d\n",((ev->type==EV_SYSEX)?"Sysex":"SysexV"),ev->sysex.len);
    midi_hexdump(ev->sysex.data,ev->sysex.len);
    fprintf(stderr,"]");
    break;
  case EV_META:
    dump_meta(ev);
    break;
  default:
    fprintf(stderr,"[Unexpected type: %d]",ev->type);
  }
}
// }}}

static inline int midistatus_len(unsigned char status) // {{{
{
  static const char lentab[]={2,2,2,2,1,1,2,0};
  return lentab[(status&0x70)>>4];
}
// }}}

// start points directly after 0xf0
// end points at 0xf7 or abortion byte
static bool set_sysex(midi_event_t *ret,unsigned char type,bool finish,unsigned char *start,unsigned char *end) // {{{
{
  ret->type=type;
  ret->channel=-1;
  ret->sysex.len=end-start;
  if (finish) {
    ret->sysex.len++;
  }
  ret->sysex.data=malloc(ret->sysex.len);
  if (!ret->sysex.data) {
    return false;
  }
  memcpy(ret->sysex.data,start,end-start);
  if (finish) {
    ret->sysex.data[end-start]=0xf7;
  }
  ret->time=0;
  return true;
}
// }}}

static int read_sysex(midi_event_t *ret,unsigned char *status,unsigned char *buf,unsigned char *end) // {{{
{
  unsigned char *pos=buf;
  // find end
  for (;pos<end;pos++) {
    if (*pos&0x80) {
      break;
    }
  }
  if (!set_sysex(ret,*status,(pos<end),buf,pos)) {
    return 0;
  }
  if (pos>=end) { // continuation needed
    *status=0xf7;
  } else if (*pos==0xf7) {
    pos++;
  } else { // aborted
    *status=0x70;
  }
  return pos-buf;
}
// }}}

void remove_realtime(unsigned char *buf,unsigned char **end) // {{{
{
  unsigned char *pos=buf,*e2=*end;
  for (;pos<e2;pos++) {
    if (*pos>=0xf8) {
      continue;
    } else if (buf!=pos) {
      *buf=*pos;
      buf++;
    }
  }
  *end=buf;
}
// }}}

static int read_voicemsg(midi_event_t *ret,unsigned char status,unsigned char *pos,unsigned char *end) // {{{
{
  const int len=midistatus_len(status);
  if ( (len==0)||(pos+len>end) ) { // catches status=0x7? and 0xf?
    return 0;
  }
  int iA;
  for (iA=0;iA<len;iA++) {
    if (pos[iA]&0x80) {
      return 0;
    }
  }
  ret->type=status&0xf0;
  ret->channel=status&0x0f;
  switch (ret->type) {
  case EV_NOTE_OFF:
  case EV_NOTE_ON:
  case EV_NOTE_AFTERTOUCH:
    ret->note.tune=pos[0];
    ret->note.value=pos[1];
    break;
  case EV_CONTROLLER:
    ret->controller.number=pos[0];
    ret->controller.value=pos[1];
    break;
  case EV_PROGRAM_CHANGE:
    ret->program.number=pos[0];
    break;
  case EV_CHANNEL_AFTERTOUCH:
    ret->channel_aftertouch.value=pos[0];
    break;
  case EV_PITCH_BEND:
    ret->pitch_bend.value=((pos[1]<<7)+pos[0])-8192;
    break;
  default:
    assert(0);
    return 0;
  }
  return len;
}
// }}}

// TODO? char-wise raw-parser?
// return consumed bytes  or 0
// requires prior removal of realtime messages
int midi_event_read_raw(midi_event_t *ret,unsigned char *status,unsigned char *buf,unsigned char *end) // {{{
{
  if ( (!ret)||(!status)||(!buf)||(buf>=end) ) {
    return 0;
  }
  clear_midi_event(ret);
  unsigned char *pos=buf;

  if (*status==0xf7) { // sysex continues
    const int len=read_sysex(ret,status,pos,end);
    if (!len) {
      return 0;
    }
    pos+=len;
  } else if ((*pos&0xf0)<0xf0) {
    if (*pos&0x80) { // new status
      *status=*pos;
      pos++;
    }
    const int len=read_voicemsg(ret,*status,pos,end);
    if (!len) {
      return 0;
    }
    pos+=len;
    ret->time=0;
  } else {
    if (*pos==EV_SYSEX) {
      *status=*pos;
      pos++;
      const int len=read_sysex(ret,status,pos,end);
      if (!len) {
        return 0;
      }
      pos+=len;
    } else if (*pos==0xf7) { // spurious SYSEX_END
      if (!set_sysex(ret,*pos,false,pos,pos+1)) {
        return 0;
      }
      pos++;
    } else { // other system common messages
      ret->type=*pos;
      ret->channel=-1;
      pos++;
      switch (ret->type) {
      case EV_MTC_QUARTERFRAME:
        if ( (pos+1>end)||(pos[0]>0x7f) ) {
          return 0;
        }
        ret->mtc_byte=pos[0];
        break;
      case EV_SONGPOSITION:
        if ( (pos+2>end)||(pos[0]>0x7f)||(pos[1]>0x7f) ) {
          return 0;
        }
        ret->songposition=(pos[1]<<7)+pos[0];
        break;
      case EV_SONGSELECT:
        if ( (pos+1>end)||(pos[0]>0x7f) ) {
          return 0;
        }
        ret->songselect=pos[0];
        break;
      case EV_TUNEREQUEST:
        break;
      default:
        fprintf(stderr,"Unknown system common message: 0x%02x\n",ret->type);
        return 0;
      }
      ret->time=0;
    }
  }
  return pos-buf;
}
// }}}

static bool read_meta(midi_event_t *ret,unsigned char *buf,int len) // {{{
{
  switch (ret->meta.type) {
  case META_SEQUENCE_NUMBER:
    if (len<2) {
      return false;
    }
    ret->meta.seqno=(buf[0]<<8)+buf[1];
    break;
  case META_STRING_TEXT:
  case META_STRING_COPYRIGHT:
  case META_STRING_NAME:
  case META_STRING_INSTRUMENT:
  case META_STRING_LYRICS:
  case META_STRING_MARKER:
  case META_STRING_CUEPOINT:
  case META_STRING_PROGRAM_NAME:
  case META_STRING_DEVICE_NAME:
    ret->meta.string.len=len;
    ret->meta.string.data=malloc(len+1);
    if (!ret->meta.string.data) {
      return false;
    }
    memcpy(ret->meta.string.data,buf,len);
    ret->meta.string.data[len]=0;
    break;
  case META_CHANNELPREFIX:
    if ( (len<1)||(buf[0]>15) ) {
      return false;
    }
    ret->meta.chan_prefix=buf[0];
    break;
  case META_PORT:
    if (len<1) {
      return false;
    }
    ret->meta.port=buf[0];
    break;
  case META_EOT:
    if (len!=0) {
      fprintf(stderr,"End-of-track with length: %d\n",len);
    }
    break;
  case META_TEMPO:
    if (len<3) {
      return false;
    }
    ret->meta.tempo.usec_per_quarter=(buf[0]<<16)+(buf[1]<<8)+buf[2];
    if (ret->meta.tempo.usec_per_quarter<100000) { // >600 BPM is bullshit
      return false;
    }
    break;
  case META_SMPTE:
    if ( (len<5)||
//         ((buf[0]>>6)>3)||((buf[0]&0x3f)>23)||    // TODO: bad file seen with buf[0]&0x3f=0x20
         (buf[1]>59)||(buf[2]>59)||(buf[3]>29)||(buf[4]>99) ) {
      return false;
    }
    ret->meta.smpte.rate=buf[0]>>6;
    ret->meta.smpte.hour=buf[0]&0x3f;
    ret->meta.smpte.minute=buf[1];
    ret->meta.smpte.second=buf[2];
    ret->meta.smpte.frame=buf[3];
    ret->meta.smpte.subframe=buf[4];
    if (  ( (ret->meta.smpte.rate==FPS_24)&&(ret->meta.smpte.frame>23) )||
          ( (ret->meta.smpte.rate==FPS_25)&&(ret->meta.smpte.frame>24) )  ) {
      return false;
    }
    break;
  case META_TIMESIG:
    if ( (len<4)||(buf[0]==0)||(buf[1]>15)||(buf[2]==0)||(buf[3]==0) ) {
      return false;
    }
    ret->meta.timesig.num=buf[0];
    ret->meta.timesig.denoml=buf[1];
    ret->meta.timesig.metro=buf[2];
    ret->meta.timesig.t32nd=buf[3];
    if (ret->meta.timesig.t32nd!=8) {
      fprintf(stderr,"Unexpected: %d 32nd per quarter(24 pulses), instead of 8\n",ret->meta.timesig.t32nd);
    }
    break;
  case META_KEYSIG:
    if ( (len<2)||(buf[1]>1) ) {
      return false;
    }
    ret->meta.keysig.sharps=(signed char)buf[0];
    ret->meta.keysig.minor=buf[1];
    if ( (ret->meta.keysig.sharps<-7)||(ret->meta.keysig.sharps>7) ) {
      return false;
    }
    break;
  default:
    fprintf(stderr,"Unknown meta type: 0x%02x, length: %d\n",ret->meta.type,len);
    ret->meta.type|=META_UNKNOWN;
  case META_USER:
    if (len<1) {
      return false;
    }
    ret->meta.user.len=len;
    ret->meta.user.data=malloc(len);
    if (!ret->meta.user.data) {
      return false;
    }
    memcpy(ret->meta.user.data,buf,len);
    break;
  }
  return true;
}
// }}}

int midi_event_read(midi_event_t *ret,unsigned char *status,unsigned char *buf,unsigned char *end) // {{{
{
  if (!ret) {
    return 0;
  }
  clear_midi_event(ret);
  if ( (!status)||(!buf)||(buf>=end) ) {
    return 0;
  }
  unsigned char *pos=buf;

  // 1. read time
  ret->time=midi_read_vl(&pos,end);
  if (ret->time<0) {
    return 0;
  }

  // 2. read data
  if ((*pos&0xf0)<0xf0) {
    if (*pos&0x80) { // new status
      *status=*pos;
      pos++;
    }
    const int len=read_voicemsg(ret,*status,pos,end);
    if (!len) {
      goto fail;
    }
    pos+=len;
  } else if ( (*pos==EV_SYSEX)||(*pos==EV_SYSEX_VERBATIM) ) {
// TODO? maybe try to process SYSEX_VERBATIM (when not in sysex extension) with _raw
    *status=0x70; // cancel any status
    ret->type=*pos;
    ret->channel=-1;
    pos++;
    const int len=midi_read_vl(&pos,end);
    if ( (len<0)||(pos+len>end) ) {
      goto fail;
    }
    ret->sysex.len=len;
    ret->sysex.data=malloc(len);
    if (!ret->sysex.data) {
      goto fail;
    }
    // check data, copy
    unsigned char *out=ret->sysex.data;
    int iA;
    for (iA=0;iA<len;iA++) {
      if (*pos>0x7f) { // bad data error
        if ( (*pos!=0xf7)||(iA+1!=len) ) { // not SYSEX_END
          fprintf(stderr,"Bad sysex data\n");
          free(ret->sysex.data);
          goto fail;
        }
      }
      *out=*pos;
      pos++;
      out++;
    }
  } else if (*pos==EV_META) {
    ret->type=*pos;
    if ((*status&0xf0)==0xf0) { // we actually have a channel from chan_prefix
      ret->channel=*status&0x0f;
    } else {
      ret->channel=-1;
      *status=0x70; // cancel any status
    }
    pos++;
    if (pos>=end) {
      goto fail;
    }
    ret->meta.type=*pos;
    pos++;
    const int len=midi_read_vl(&pos,end);
    if ( (len<0)||(pos+len>end)||
         (!read_meta(ret,pos,len)) ) {
      goto fail;
    }
    if (ret->meta.type==META_CHANNELPREFIX) {
      *status=0xf0+ret->meta.chan_prefix;
    }
    pos+=len;
  } else {
    fprintf(stderr,"Unexpected non-midi event: 0x%02x\n",*pos);
    assert(0);
    goto fail;
  }
  return pos-buf;
fail:
  ret->time=-1;
  return 0;
}
// }}}

// returns size, -1 on EOT, or 0 on error
int midi_event_skip(unsigned char *status,unsigned char *buf,unsigned char *end) // {{{
{
  assert( (status)&&(buf) );
  unsigned char *pos=buf;

  // 1. skip time
  if (midi_read_vl(&pos,end)<0) {
    return 0;
  }

  // 2. skip data
  if (pos>=end) {
    return 0;
  }
  if ((*pos&0xf0)<0xf0) {
    if (*pos&0x80) { // new status
      *status=*pos;
      pos++;
    }
    pos+=midistatus_len(*status);
  } else {
    *status=0x70; // cancel any status
    int len=-1;
    if ( (*pos==EV_SYSEX)||(*pos==EV_SYSEX_VERBATIM) ) {
      pos++;
      len=midi_read_vl(&pos,end); // length
    } else if (*pos==EV_META) {
      pos++;
      if ( (pos<end)&&(*pos==META_EOT) ) { // NOTE: we do not check len=0 here
        return -1;
      }
      pos++;
      len=midi_read_vl(&pos,end); // length
    }
    if (len<0) {
      return 0;
    }
    pos+=len;
  }
  if (pos>=end) {
    return 0;
  }
  return (pos-buf);
}
// }}}

// returns out[0], out[1] for len>=1, out[2] for len=2
static int write_voicemsg(midi_event_t *ev,unsigned char *out) // {{{
{
  const int len=midistatus_len(ev->type);
  out[0]=ev->type|ev->channel;
  switch (ev->type) {
  case EV_NOTE_OFF:
  case EV_NOTE_ON:
  case EV_NOTE_AFTERTOUCH:
    out[1]=ev->note.tune;
    out[2]=ev->note.value;
    break;
  case EV_CONTROLLER:
    out[1]=ev->controller.number;
    out[2]=ev->controller.value;
    break;
  case EV_PROGRAM_CHANGE:
    out[1]=ev->program.number;
    break;
  case EV_CHANNEL_AFTERTOUCH:
    out[1]=ev->channel_aftertouch.value;
    break;
  case EV_PITCH_BEND:
    if ( (ev->pitch_bend.value<-8192)||(ev->pitch_bend.value>8191) ) {
      return 0;
    }
    out[1]=(ev->pitch_bend.value+8192)&0x7f;
    out[2]=(ev->pitch_bend.value+8192)>>7;
    break;
  default:
    assert(0);
    return 0;
  }
  if ( (ev->channel<0)||(ev->channel>15)||(out[1]>0x7f)||(out[2]>0x7f) ) {
    return 0;
  }
  return len;
}
// }}}

// TODO: sysex
// return written bytes  or 0;  allow buf==end==0 for size determination;  allow status==0 to disable running status
int midi_event_write_raw(midi_event_t *ev,unsigned char *status,unsigned char *buf,unsigned char *end) // {{{
{
  if ( (!ev)||(ev->time<0) )  {
    return 0;
  }
  if (ev->type>=EV_SYSEX) {
    // if (ev->type==EV_SYSEX_VERBATIM) {
    // if (ev->type==EV_META) {
assert(0); // TODO
    return 0;
  }
  unsigned char out[3]={0,0,0};
  int len=write_voicemsg(ev,out);
  if (!len) {
    return 0;
  }
  // running status
  if ( (!status)||(*status!=out[0]) ) {
    if (status) {
      *status=out[0];
    }
    status=NULL;
    len++;
  }
  if (!buf) {
    return len;
  } else if (buf+len>end) {
    return 0;
  }
  memcpy(buf,out+((status)?1:0),len);
  return len;
}
// }}}

// returns data length  or -1
static int meta_length(midi_event_t *ev) // {{{
{
  if ( (!ev)||(ev->type!=EV_META) ) {
    return -1;
  }
  switch (ev->meta.type) {
  case META_SEQUENCE_NUMBER:
    return 2;
  case META_STRING_TEXT:
  case META_STRING_COPYRIGHT:
  case META_STRING_NAME:
  case META_STRING_INSTRUMENT:
  case META_STRING_LYRICS:
  case META_STRING_MARKER:
  case META_STRING_CUEPOINT:
  case META_STRING_PROGRAM_NAME:
  case META_STRING_DEVICE_NAME:
    return ev->meta.string.len;
  case META_CHANNELPREFIX:
  case META_PORT:
    return 1;
  case META_EOT:
    return 0;
  case META_TEMPO:
    return 3;
  case META_SMPTE:
    return 5;
  case META_TIMESIG:
    return 4;
  case META_KEYSIG:
    return 2;
  case META_USER:
    return ev->meta.user.len;
  default:
    if (ev->meta.type&META_UNKNOWN) {
      return ev->meta.user.len;
    } else {
      fprintf(stderr,"Unknown meta: %d\n",ev->meta.type);
    }
  }
  return -1;
}
// }}}

// returns written bytes  or 0;  allows buf==end==0 for size determination
static int write_meta(midi_event_t *ev,unsigned char *out,unsigned char *end) // {{{
{
  assert( (ev)&&(ev->type==EV_META) );
  const int len=meta_length(ev);
  if (!out) {
    return 2+midi_write_vl(NULL,NULL,len)+len; // 0xff type length... data...
  }

  if (out+2>=end) {
    return 0;
  }

  // 0xff type
  out[0]=ev->type;
  out[1]=(ev->meta.type&0xff);
  out+=2;
  // length
  int res=midi_write_vl(out,end,len);
  if ( (res==0)||(out+res+len>end) ) {
    return 0;
  }
  out+=res;

  // data
  switch (ev->meta.type) {
  case META_SEQUENCE_NUMBER:
    out[0]=(ev->meta.seqno>>8)&0xff;
    out[1]=(ev->meta.seqno)&0xff;
    break;
  case META_STRING_TEXT:
  case META_STRING_COPYRIGHT:
  case META_STRING_NAME:
  case META_STRING_INSTRUMENT: // maybe with META_CHANNELPREFIX
  case META_STRING_LYRICS:
  case META_STRING_MARKER:
  case META_STRING_CUEPOINT:
  case META_STRING_PROGRAM_NAME:
  case META_STRING_DEVICE_NAME:
    if (!ev->meta.string.data) {
      return 0;
    }
    memcpy(out,ev->meta.string.data,ev->meta.string.len);
    break;
  case META_CHANNELPREFIX:
    if (ev->meta.chan_prefix>15) {
      return 0;
    }
    out[0]=ev->meta.chan_prefix;
    break;
  case META_PORT:
    out[0]=ev->meta.port;
    break;
  case META_EOT:
    break;
  case META_TEMPO:
    if ( (ev->meta.tempo.usec_per_quarter<100000)||   // 600 BPM?  WTF?
         (ev->meta.tempo.usec_per_quarter>0xffffff) ) {
      return 0;
    }
    out[0]=(ev->meta.tempo.usec_per_quarter>>16)&0xff;
    out[1]=(ev->meta.tempo.usec_per_quarter>>8)&0xff;
    out[2]=(ev->meta.tempo.usec_per_quarter)&0xff;
    break;
  case META_SMPTE:
    if (  ( (ev->meta.smpte.rate==FPS_24)&&(ev->meta.smpte.frame>23) )||
          ( (ev->meta.smpte.rate==FPS_25)&&(ev->meta.smpte.frame>24) )  ) {
      return 0;
    }
    if ( (ev->meta.smpte.rate<FPS_24)||(ev->meta.smpte.rate>FPS_30)||
//         (ev->meta.smpte.hour>23)||  // TODO: bad file seen with hour=0x20
         (ev->meta.smpte.minute>59)||
         (ev->meta.smpte.second>59)||
         (ev->meta.smpte.frame>29)||
         (ev->meta.smpte.subframe>99) ) {
      return 0;
    }
    out[0]=(ev->meta.smpte.rate<<6)|(ev->meta.smpte.hour&0x3f);
    out[1]=ev->meta.smpte.minute;
    out[2]=ev->meta.smpte.second;
    out[3]=ev->meta.smpte.frame;
    out[4]=ev->meta.smpte.subframe;
    break;
  case META_TIMESIG:
    if ( (ev->meta.timesig.num==0)||
         (ev->meta.timesig.denoml>15)||
         (ev->meta.timesig.metro==0)||
         (ev->meta.timesig.t32nd==0) ) {
      return 0;
    }
    out[0]=ev->meta.timesig.num;
    out[1]=ev->meta.timesig.denoml;
    out[2]=ev->meta.timesig.metro;
    out[3]=ev->meta.timesig.t32nd;
    break;
  case META_KEYSIG:
    if ( (ev->meta.keysig.sharps<-7)||(ev->meta.keysig.sharps>7) ) {
      return 0;
    }
    out[0]=(unsigned char)ev->meta.keysig.sharps;
    out[1]=(ev->meta.keysig.minor)?1:0;
    break;
  default:
    if ((ev->meta.type&META_UNKNOWN)==0) {
      fprintf(stderr,"Unknown meta: %d\n",ev->meta.type);
      return 0;
    }
  case META_USER:
    if (!ev->meta.user.data) {
      return 0;
    }
    memcpy(out,ev->meta.user.data,ev->meta.user.len);
    break;
  }
  return 2+res+len;
}
// }}}

int midi_event_write(midi_event_t *ev,unsigned char *status,unsigned char *buf,unsigned char *end) // {{{
{
  if ( (!ev)||(ev->time<0) ) {
    return 0;
  }
  int len=0,res;

  // 1. write time
  len+=res=midi_write_vl(buf,end,ev->time);
  if (!res) {
    return 0;
  } else if (buf) {
    buf+=res;
  }

  // 2. write data
  if (ev->type==EV_META) {
    if ( (ev->channel!=-1)&&(*status) ) { // only synthesize if status!=0
      if ( ((*status&0xf0)!=0xf0)||((*status&0x0f)!=ev->channel) ) { // we need a (new) chan_prefix
        // synthesize
        midi_event_t cpev;
        init_midi_event(&cpev);
        // time is already written!
        cpev.time=0;
        cpev.type=EV_META;
        cpev.meta.type=META_CHANNELPREFIX;
        cpev.meta.chan_prefix=ev->channel;
        len+=res=write_meta(&cpev,buf,end);
        if (res==0) {
          return 0;
        } else if (buf) {
          buf+=res;
        }
        // don't forget to write time again (==0)
        if (buf<end) {
          *buf=0;
          buf++;
        }
        len++;
        *status=0xf0|ev->channel;
      }
    } else if (*status) {
      *status=0x70; // cancel any status
    }
    len+=res=write_meta(ev,buf,end);
    if (res==0) {
      return 0;
    } else if (buf) {
      buf+=res;
    }
  } else if (ev->type>=EV_SYSEX) {
    // if (ev->type==EV_SYSEX_VERBATIM) {
assert(0); // TODO
    return 0;
  } else {
    unsigned char out[3]={0,0,0};
    res=write_voicemsg(ev,out);
    if (!res) {
      return 0;
    }
    // running status
    if ( (!status)||(*status!=out[0]) ) {
      if (status) {
        *status=out[0];
      }
      status=NULL;
      res++;
    }
    len+=res;
    if (buf) {
      if (buf+res>end) {
        return 0;
      }
      memcpy(buf,out+((status)?1:0),res);
      // buf+=res;
    }
  }
  return len;
#if 0
  // 2. read data
  } else if ( (*pos==EV_SYSEX)||(*pos==EV_SYSEX_VERBATIM) ) {
// TODO? maybe try to process SYSEX_VERBATIM (when not in sysex extension) with _raw
    *status=0x70; // cancel any status
    ret->type=*pos;
    ret->channel=-1;
    pos++;
    const int len=midi_read_vl(&pos,end);
    if ( (len<0)||(pos+len>end) ) {
      goto fail;
    }
    ret->sysex.len=len;
    ret->sysex.data=malloc(len);
    if (!ret->sysex.data) {
      goto fail;
    }
    // check data, copy
    unsigned char *out=ret->sysex.data;
    int iA;
    for (iA=0;iA<len;iA++) {
      if (*pos>0x7f) { // bad data error
        if ( (*pos!=0xf7)||(iA+1!=len) ) { // not SYSEX_END
          fprintf(stderr,"Bad sysex data\n");
          free(ret->sysex.data);
          goto fail;
        }
      }
      *out=*pos;
      pos++;
      out++;
    }
  } else {
    fprintf(stderr,"Unexpected non-midi event: 0x%02x\n",*pos);
    assert(0);
    goto fail;
  }
#endif
}
// }}}
