/*
    midifile.c:

    Copyright (C) 2005 Istvan Varga

    This file is part of Csound.

    The Csound Library is free software; you can redistribute it
    and/or modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    Csound is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with Csound; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
    02110-1301 USA
*/

#include "csoundCore.h"
#include "midifile.h"
#include <errno.h>

static const char *midiFile_ID = "MThd";
static const char *midiTrack_ID = "MTrk";
/* default tempo in beats per minute */
static const double default_tempo = 120.0;

typedef struct tempoEvent_s {
    unsigned long   kcnt;               /* time (in ticks while reading     */
                                        /*   MIDI file, will be converted   */
                                        /*   to kperiods once file is read) */
    double          tempoVal;           /* tempo value in beats per minute  */
} tempoEvent_t;

typedef struct midiEvent_s {
    unsigned long   kcnt;               /* time (in ticks while reading     */
                                        /*   MIDI file, will be converted   */
                                        /*   to kperiods once file is read) */
#if 0
    unsigned char   *data;              /* pointer to sysex or meta event   */
                                        /*   data (currently not used)      */
#endif
    unsigned char   st;                 /* status byte (0x80-0xFF)          */
    unsigned char   d1;                 /* data byte 1 (0x00-0x7F)          */
    unsigned char   d2;                 /* data byte 2 (0x00-0x7F)          */
} midiEvent_t;

typedef struct midiFile_s {
    /* static file data, not changed at performance */
    double          timeCode;           /* > 0: ticks per beat              */
                                        /* < 0: ticks per second            */
    unsigned long   totalKcnt;          /* total duration of file           */
                                        /*   (in ticks while reading file,  */
                                        /*   converted to kperiods)         */
    int             nEvents;            /* number of events                 */
    int             maxEvents;          /* event array size                 */
    int             nTempo;             /* number of tempo changes          */
    int             maxTempo;           /* tempo change array size          */
    midiEvent_t     *eventList;         /* array of MIDI events             */
    tempoEvent_t    *tempoList;         /* array of tempo changes           */
    /* performance time state variables */
    double          currentTempo;       /* current tempo in BPM             */
    int             eventListIndex;     /* index of next MIDI event in list */
    int             tempoListIndex;     /* index of next tempo change       */
} midiFile_t;

#define MIDIFILE    (csound->midiGlobals->midiFileData)
#define MF(x)       (((midiFile_t*) MIDIFILE)->x)

static int getCh(CSOUND *csound, FILE *f, int *bytesLeft)
{
    int c;

    if (f == NULL)
      return -1;
    c = getc(f);
    if (UNLIKELY(c == EOF)) {
      csound->Message(csound, Str(" *** unexpected end of MIDI file\n"));
      return -1;
    }
    if (bytesLeft != NULL) {
      if (UNLIKELY(--(*bytesLeft) < 0)) {
        csound->Message(csound, Str(" *** unexpected end of MIDI track\n"));
        return -1;
      }
    }
    return (c & 0xFF);
}

static int getVLenData(CSOUND *csound, FILE *f, int *bytesLeft)
{
    int c, n, cnt;

    n = cnt = 0;
    do {
      if (UNLIKELY(++cnt > 4)) {
        csound->Message(csound,
                        Str(" *** invalid dynamic length data in MIDI file\n"));
        return -1;
      }
      c = getCh(csound, f, bytesLeft);
      if (c < 0)
        return -1;
      n = (n << 7) | (c & 0x7F);
    } while (c & 0x80);
    return n;
}

/* return the number of bytes to read for status byte 'c' */
/* return value is 0, 1, 2, or -1 in the case of an unknown */
/* or special message */

static int msgDataBytes(int c)
{
    switch (c & 0xF0) {
      case 0x80:        /* note off */
      case 0x90:        /* note on */
      case 0xA0:        /* polyphonic pressure */
      case 0xB0:        /* control change */
      case 0xE0:        /* pitch bend */
        return 2;
      case 0xC0:        /* program change */
      case 0xD0:        /* channel pressure */
        return 1;
      case 0xF0:
        switch (c) {
          case 0xF2:    /* song position */
            return 2;
          case 0xF1:    /* MTC quarter frame */
          case 0xF3:    /* song select */
            return 1;
          case 0xF0:    /* system exclusive */
          case 0xF7:    /* escape sequence */
          case 0xFF:    /* meta event */
          case 0xF4:    /* unknown */
          case 0xF5:    /* unknown */
          case 0xF9:    /* unknown */
          case 0xFD:    /* unknown */
            return -1;
          default:      /* tune request, real time system messages */
            return 0;
        }
    }
    /* anything else is unknown */
    return -1;
}

static int alloc_event(CSOUND *csound, unsigned long kcnt, unsigned char *data,
                                       int st, int d1, int d2)
{
    midiEvent_t *tmp;
    IGN(data);
    /* expand array if necessary */
    if (MF(nEvents) >= MF(maxEvents)) {
      MF(maxEvents) += (MF(maxEvents) >> 3);
      MF(maxEvents) = (MF(maxEvents) + 64) & (~63);
      tmp = (midiEvent_t*) csound->ReAlloc(csound, MF(eventList),
                                    sizeof(midiEvent_t) * MF(maxEvents));
      MF(eventList) = tmp;
      tmp = &(MF(eventList)[MF(nEvents)]);
      memset(tmp, 0, sizeof(midiEvent_t) * (MF(maxEvents) - MF(nEvents)));
    }
    /* store new event */
    tmp = &(MF(eventList)[MF(nEvents)]);
    MF(nEvents)++;
    tmp->kcnt = kcnt;
 /* tmp->data = data; */        /* not used yet */
    tmp->st = (unsigned char) st;
    tmp->d1 = (unsigned char) d1;
    tmp->d2 = (unsigned char) d2;
    /* done */
    return 0;
}

static int alloc_tempo(CSOUND *csound, unsigned long kcnt, double tempoVal)
{
    tempoEvent_t *tmp;
    /* expand array if necessary */
    if (MF(nTempo) >= MF(maxTempo)) {
      MF(maxTempo) += (MF(maxTempo) >> 3);
      MF(maxTempo) = (MF(maxTempo) + 64) & (~63);
      tmp = (tempoEvent_t*) csound->ReAlloc(csound, MF(tempoList),
                                     sizeof(tempoEvent_t) * MF(maxTempo));
      MF(tempoList) = tmp;
      tmp = &(MF(tempoList)[MF(nTempo)]);
      memset(tmp, 0, sizeof(tempoEvent_t) * (MF(maxTempo) - MF(nTempo)));
    }
    /* store new event */
    tmp = &(MF(tempoList)[MF(nTempo)]);
    MF(nTempo)++;
    tmp->kcnt = kcnt; tmp->tempoVal = tempoVal;
    /* done */
    return 0;
}

static int readEvent(CSOUND *csound, FILE *f, int *tlen,
                     unsigned long tickCnt, int st, int *saved_st);

static int checkRealTimeEvent(CSOUND *csound, FILE *f, int *tlen,
                              unsigned long tickCnt, int st, int *saved_st)
{
    if (st & 0x80) {
      if (UNLIKELY(st < 0xF8 || st > 0xFE)) {
        csound->Message(csound, Str(" *** unexpected event 0x%02X\n"),
                                (unsigned int) st);
        return -1;
      }
      /* handle real time message (return code -2) */
      if (readEvent(csound, f, tlen, tickCnt, st, saved_st) != 0)
        return -1;
      return -2;
    }
    return st;
}

static int readEvent(CSOUND *csound, FILE *f, int *tlen,
                     unsigned long tickCnt, int st, int *saved_st)
{
    int i, c, d, cnt, dataBytes[2];

    cnt = dataBytes[0] = dataBytes[1] = 0;
    if (st < 0x80) {
      /* repeat previous status byte */
      dataBytes[cnt++] = st;
      st = *saved_st;
      if (UNLIKELY(st < 0x80)) {
        csound->Message(csound, Str(" *** invalid MIDI file data\n"));
        return -1;
      }
    }
    c = msgDataBytes(st);
    if (c >= 0) {
      if (c > 0) {
        /* save status byte for repeat */
        *saved_st = st;
      }
      while (cnt < c) {
        /* read data byte(s) */
        d = getCh(csound, f, tlen);
        if (d < 0 || *tlen < 0) return -1;
        d = checkRealTimeEvent(csound, f, tlen, tickCnt, d, saved_st);
        if (d == -2)    /* read real time event: continue with reading data */
          continue;
        if (d < 0) return -1;
        dataBytes[cnt++] = d;
      }
      return alloc_event(csound, tickCnt, NULL, st,
                         dataBytes[0], dataBytes[1]);
    }
    /* message is of unknown or special type */
    if (st == 0xF0) {
      /* system exclusive */
      i = getVLenData(csound, f, tlen);
      if (i < 0 || *tlen < 0) return -1;
      /* read message */
      while (--i >= 0) {
        d = getCh(csound, f, tlen);
        if (d < 0 || *tlen < 0) return -1;
        if (d == 0xF7) {        /* EOX */
          if (LIKELY(!i))
            return 0;           /* should be at end of message */
          csound->Message(csound, Str(" *** unexpected end of system "
                                      "exclusive message\n"));
          return -1;
        }
        d = checkRealTimeEvent(csound, f, tlen, tickCnt, d, saved_st);
        if (d == -2)            /* if read real time event, */
          i++;                  /* continue with reading message bytes */
        else if (UNLIKELY(d < 0))
          return -1;            /* error */
      }
      /* zero length or EOX not found */
      csound->Message(csound, Str(" *** invalid system exclusive "
                                  "message in MIDI file\n"));
      return -1;
    }
    else if (st == 0xF7) {
      /* escape sequence: skip message */
      i = getVLenData(csound, f, tlen);         /* message length */
      if (i < 0 || *tlen < 0) return -1;
      while (--i >= 0) {
        c = getCh(csound, f, tlen);
        if (c < 0 || *tlen < 0) return -1;
      }
      return 0;
    }
    else if (st == 0xFF) {
      /* meta event */
      st = getCh(csound, f, tlen);              /* message type */
      if (st < 0 || *tlen < 0) return -1;
      i = getVLenData(csound, f, tlen);         /* message length */
      if (i < 0 || *tlen < 0) return -1;
      if (i > 0 &&
          ((st >= 1 && st <= 5 && (csound->oparms->msglevel & 7) == 7) ||
           (st == 3 && csound->oparms->msglevel != 0))) {
        /* print non-empty text meta events, depending on message level */
        switch (st) {
          case 0x01: csound->Message(csound, Str("  Message: ")); break;
          case 0x02: csound->Message(csound, Str("  Copyright info: ")); break;
          case 0x03: csound->Message(csound, Str("  Track name: ")); break;
          case 0x04: csound->Message(csound, Str("  Instrument name: ")); break;
          case 0x05: csound->Message(csound, Str("  Song lyric: ")); break;
        }
        while (--i >= 0) {
          c = getCh(csound, f, tlen);
          if (c < 0 || *tlen < 0) return -1;
          csound->Message(csound, "%c", c);
        }
        csound->Message(csound, "\n");
        return 0;
      }
      switch (st) {
        case 0x51:                        /* tempo change */
          d = 0;
          while (--i >= 0) {
            c = getCh(csound, f, tlen);
            if (c < 0 || *tlen < 0) return -1;
            d = (d << 8) | c;
          }
          if (UNLIKELY(d < 1)) {
            csound->Message(csound, Str(" *** invalid tempo\n"));
            return -1;
          }
          return alloc_tempo(csound, tickCnt, (60000000.0 / (double) d));
        case 0x2F:                        /* end of track */
          if (UNLIKELY(i)) {
            csound->Message(csound, Str(" *** invalid end of track event\n"));
            return -1;
          }
          if (UNLIKELY(*tlen > 0)) {
            csound->Message(csound, Str(" *** trailing garbage at end of "
                                        "MIDI track\n"));
            return -1;
          }
          /* update file length info */
          if (tickCnt > MF(totalKcnt))
            MF(totalKcnt) = tickCnt;
          return 0;
        default:                          /* skip any other meta event */
          while (--i >= 0) {
            c = getCh(csound, f, tlen);
            if (c < 0 || *tlen < 0) return -1;
          }
          return 0;
      }
    }
    csound->Message(csound, Str(" *** unknown MIDI message: 0x%02X\n"),
                            (unsigned int) st);
    return -1;
}

static int readTrack(CSOUND *csound, FILE *f)
{
    unsigned int    tickCnt;
    int             i, c, tlen, st, saved_st;

    /* check for track header */
    for (i = 0; i < 4; i++) {
      c = getCh(csound, f, NULL);
      if (c < 0)
        return -1;
      if (UNLIKELY(c != (int) midiTrack_ID[i])) {
        csound->Message(csound, Str(" *** invalid MIDI track header\n"));
        return -1;
      }
    }
    /* read track length */
    tlen = 0;
    for (i = 0; i < 4; i++) {
      c = getCh(csound, f, NULL);
      if (c < 0)
        return -1;
      tlen = (tlen << 8) | c;
    }
    /* read track data */
    tickCnt = 0UL;
    saved_st = -1;
    while (tlen > 0) {
      /* get delta time */
      c = getVLenData(csound, f, &tlen);
      if (c < 0 || tlen < 0)
        return -1;
      tickCnt += (unsigned long) c;
      /* get status byte */
      st = getCh(csound, f, &tlen);
      if (st < 0 || tlen < 0)
        return -1;
      /* process event */
      if (readEvent(csound, f, &tlen, tickCnt, st, &saved_st) != 0)
        return -1;
    }
    /* successfully read track */
    return 0;
}

/**
 * Sorts an array of midiEvent_t structures using merge sort algorithm
 * so that the order of events at the same time is preserved.
 * 'p' is the array to be sorted, and 'cnt' is the number of elements
 * (should be > 1). 'tmp' is temporary space of the same length as 'p'.
 */

static CS_NOINLINE void midiEvent_sort(midiEvent_t *p, midiEvent_t *tmp,
                                       size_t cnt)
{
    size_t  p1, p2, dstp;
    size_t  n = cnt >> 1;

    if (n > (size_t) 1)
      midiEvent_sort(p, tmp, n);
    if (cnt > (size_t) 2)
      midiEvent_sort(&(p[n]), tmp, cnt - n);
    dstp = (size_t) 0;
    p1 = (size_t) 0;
    p2 = n;
    do {
      size_t  srcp;
      if (p2 >= cnt || (p1 < n && p[p1].kcnt <= p[p2].kcnt))
        srcp = p1++;
      else
        srcp = p2++;
      tmp[dstp] = p[srcp];
    } while (++dstp < cnt);
    /* copy result back to original array */
    memcpy(p, tmp, cnt * sizeof(midiEvent_t));
}

/**
 * Sorts an array of tempoEvent_t structures using merge sort algorithm
 * so that the order of events at the same time is preserved.
 * 'p' is the array to be sorted, and 'cnt' is the number of elements
 * (should be > 1). 'tmp' is temporary space of the same length as 'p'.
 */

static CS_NOINLINE void tempoEvent_sort(tempoEvent_t *p, tempoEvent_t *tmp,
                                        size_t cnt)
{
    size_t  p1, p2, dstp;
    size_t  n = cnt >> 1;

    if (n > (size_t) 1)
      tempoEvent_sort(p, tmp, n);
    if (cnt > (size_t) 2)
      tempoEvent_sort(&(p[n]), tmp, cnt - n);
    dstp = (size_t) 0;
    p1 = (size_t) 0;
    p2 = n;
    do {
      size_t  srcp;
      if (p2 >= cnt || (p1 < n && p[p1].kcnt <= p[p2].kcnt))
        srcp = p1++;
      else
        srcp = p2++;
      tmp[dstp] = p[srcp];
    } while (++dstp < cnt);
    /* copy result back to original array */
    memcpy(p, tmp, cnt * sizeof(tempoEvent_t));
}

/* sort event lists by time and convert tick times to Csound k-periods */

static void sortEventLists(CSOUND *csound)
{
    double        timeVal, tempoVal;
    unsigned long prvTicks, curTicks, tickEvent, tickTempo;
    int           i, j;

    /* sort events by time in ascending order */
    if (MF(nEvents) > 1 || MF(nTempo) > 1) {
      void    *tmp;
      size_t  nbytes;
      nbytes = (size_t) MF(nEvents) * sizeof(midiEvent_t);
      if (((size_t) MF(nTempo) * sizeof(tempoEvent_t)) > nbytes)
        nbytes = (size_t) MF(nTempo) * sizeof(tempoEvent_t);
      tmp = csound->Malloc(csound, nbytes);
      if (MF(nEvents) > 1)
        midiEvent_sort(MF(eventList), (midiEvent_t *) tmp,
                       (size_t) MF(nEvents));
      if (MF(nTempo) > 1)
        tempoEvent_sort(MF(tempoList), (tempoEvent_t *) tmp,
                        (size_t) MF(nTempo));
      csound->Free(csound, tmp);
    }
    if (MF(timeCode) > 0.0) {
      /* tick values are in fractions of a beat */
      timeVal = 0.0;
      tempoVal = default_tempo;
      prvTicks = curTicks = 0UL;
      /* k-periods per tick */
      tempoVal = (double) csound->ekr / (tempoVal * MF(timeCode) / 60.0);
      i = j = 0;
      while (i < MF(nEvents) || j < MF(nTempo)) {
        prvTicks = curTicks;
        tickEvent = tickTempo = 0UL;
        tickEvent--; tickTempo--;   /* will be max value for unsigned long */
        if (i < MF(nEvents)) tickEvent = MF(eventList)[i].kcnt;
        if (j < MF(nTempo)) tickTempo = MF(tempoList)[j].kcnt;
        if (tickEvent < tickTempo) {
          curTicks = tickEvent;
          timeVal += ((double) ((long) (curTicks - prvTicks)) * tempoVal);
          MF(eventList)[i++].kcnt = (unsigned long) (timeVal + 0.5);
        }
        else {
          curTicks = tickTempo;
          timeVal += ((double) ((long) (curTicks - prvTicks)) * tempoVal);
          tempoVal = MF(tempoList)[j].tempoVal;     /* new tempo */
          /* k-periods per tick */
          tempoVal = (double) csound->ekr / (tempoVal * MF(timeCode) / 60.0);
          MF(tempoList)[j++].kcnt = (unsigned long) (timeVal + 0.5);
        }
      }
      /* calculate total file length in k-periods */
      timeVal += ((double) ((long) (MF(totalKcnt) - curTicks)) * tempoVal);
      MF(totalKcnt) = (unsigned long) (timeVal + 0.5);
    }
    else {
      /* simple case: time based tick values */
      tempoVal = -(MF(timeCode));
      /* k-periods per tick */
      tempoVal = (double) csound->ekr / tempoVal;
      i = -1;
      while (++i < MF(nEvents)) {
        curTicks = MF(eventList)[i].kcnt;
        timeVal = (double) curTicks * tempoVal;
        curTicks = (unsigned long) (timeVal + 0.5);
        MF(eventList)[i].kcnt = curTicks;
      }
      i = -1;
      while (++i < MF(nTempo)) {
        curTicks = MF(tempoList)[i].kcnt;
        timeVal = (double) curTicks * tempoVal;
        curTicks = (unsigned long) (timeVal + 0.5);
        MF(tempoList)[i].kcnt = curTicks;
      }
      /* calculate total file length in k-periods */
      MF(totalKcnt) = (unsigned long) ((double) MF(totalKcnt) * tempoVal + 0.5);
    }
}

 /* ------------------------------------------------------------------------ */

/* open MIDI file, read all tracks, and create event list */

int csoundMIDIFileOpen(CSOUND *csound, const char *name)
{
    FILE    *f = NULL;
    void    *fd = NULL;
    char    *m;
    int     i, c, hdrLen, fileFormat, nTracks, timeCode, saved_nEvents;
    int     mute_track;

    if (MIDIFILE != NULL)
      return 0;         /* already opened */
    /* open file */
    if (UNLIKELY(name == NULL || name[0] == '\0'))
      return -1;
    //if (*name==3) name++;       /* Because of ETX added bt readOptions */
    if (strcmp(name, "stdin") == 0)
      f = stdin;
    else {
      fd = csound->FileOpen2(csound, &f, CSFILE_STD, name, "rb",
                             "SFDIR;SSDIR;MFDIR", CSFTYPE_STD_MIDI, 0);
      if (UNLIKELY(fd == NULL)) {
        csound->ErrorMsg(csound, Str(" *** error opening MIDI file '%s': %s"),
                                 name, strerror(errno));
        return -1;
      }
    }
    csound->Message(csound, Str("Reading MIDI file '%s'...\n"), name);
    /* check header */
    for (i = 0; i < 4; i++) {
      c = getCh(csound, f, NULL);
      if (UNLIKELY(c < 0)) goto err_return;
      if (UNLIKELY(c != (int) midiFile_ID[i])) {
        csound->Message(csound, Str(" *** invalid MIDI file header\n"));
        goto err_return;
      }
    }
    /* header length: must be 6 bytes */
    hdrLen = 0;
    for (i = 0; i < 4; i++) {
      c = getCh(csound, f, NULL);
      if (UNLIKELY(c < 0)) goto err_return;
      hdrLen = (hdrLen << 8) | c;
    }
    if (UNLIKELY(hdrLen != 6)) {
      csound->Message(csound, Str(" *** invalid MIDI file header\n"));
      goto err_return;
    }
    /* file format (only 0 and 1 are supported) */
    fileFormat = 0;
    for (i = 0; i < 2; i++) {
      c = getCh(csound, f, NULL);
      if (UNLIKELY(c < 0)) goto err_return;
      fileFormat = (fileFormat << 8) | c;
    }
    if (UNLIKELY(fileFormat != 0 && fileFormat != 1)) {
      csound->Message(csound,
                      Str(" *** MIDI file format %d is not supported\n"),
                      fileFormat);
      goto err_return;
    }
    /* number of tracks */
    nTracks = 0;
    for (i = 0; i < 2; i++) {
      c = getCh(csound, f, NULL);
      if (UNLIKELY(c < 0)) goto err_return;
      nTracks = (nTracks << 8) | c;
    }
    if (UNLIKELY(nTracks < 1)) {
      csound->Message(csound, Str(" *** invalid number of tracks\n"));
      goto err_return;
    }
    if (UNLIKELY(nTracks > 1 && !fileFormat)) {
      csound->Message(csound, Str("WARNING: format 0 MIDI file with "
                                  "multiple tracks\n"));
    }
    /* time code */
    timeCode = 0;
    for (i = 0; i < 2; i++) {
      c = getCh(csound, f, NULL);
      if (UNLIKELY(c < 0)) goto err_return;
      timeCode = (timeCode << 8) | c;
    }
    /* allocate structure */
    MIDIFILE = (void*) csound->Calloc(csound, sizeof(midiFile_t));
    /* calculate ticks per second or beat based on time code */
    if (UNLIKELY(timeCode < 1 || (timeCode >= 0x8000 && (timeCode & 0xFF) == 0))) {
      csound->Message(csound, Str(" *** invalid time code: %d\n"), timeCode);
      goto err_return;
    }
    if (timeCode < 0x8000)
      MF(timeCode) = (double) timeCode;
    else {
      switch (timeCode & 0xFF00) {
        case 0xE800:
        case 0xE700:
        case 0xE200:
          MF(timeCode) = (double) ((timeCode >> 8) - 256);
          break;
        case 0xE300:
          MF(timeCode) = -29.97;
          break;
        default:
          csound->Message(csound,
                          Str(" *** invalid time code: %d\n"), timeCode);
          goto err_return;
      }
      MF(timeCode) *= (double) (timeCode & 0xFF);
    }
    /* initialise structure data */
    MF(totalKcnt) = csound->global_kcounter;
    MF(nEvents) = 0; MF(maxEvents) = 0;
    MF(nTempo) = 0; MF(maxTempo) = 0;
    MF(eventList) = (midiEvent_t*) NULL;
    MF(tempoList) = (tempoEvent_t*) NULL;
    MF(currentTempo) = default_tempo;
    MF(eventListIndex) = 0;
    MF(tempoListIndex) = 0;
    /* read all tracks */
    m = &(csound->midiGlobals->muteTrackList[0]);
    for (i = 0; i < nTracks; i++) {
      saved_nEvents = MF(nEvents);
      mute_track = 0;
      if (*m != '\0') {             /* is this track muted ? */
        if (*m == '1')
          mute_track = 1;
        else if (UNLIKELY(*m != '0')) {
          csound->Message(csound, Str(" *** invalid mute track list format\n"));
          goto err_return;
        }
        m++;
      }
      if (!mute_track)
        csound->Message(csound, Str(" Track %2d\n"), i);
      else
        csound->Message(csound, Str(" Track %2d is muted\n"), i);
      if (readTrack(csound, f) != 0)
        goto err_return;
      if (mute_track)                   /* if track is muted, discard any */
        MF(nEvents) = saved_nEvents;    /* non-tempo events read */
    }
    if (fd != NULL)
      csound->FileClose(csound, fd);
    /* prepare event and tempo list for reading */
    sortEventLists(csound);
    /* successfully read MIDI file */
    csound->Message(csound, Str("done.\n"));
    return 0;

    /* in case of error: clean up and report error */
 err_return:
    if (fd != NULL)
      csound->FileClose(csound, fd);
    csoundMIDIFileClose(csound);
    return -1;
}

/* read MIDI file event data at performace time */

int csoundMIDIFileRead(CSOUND *csound, unsigned char *buf, int nBytes)
{
    midiFile_t  *mf;
    int         i, j, n, nRead;

    mf = (midiFile_t*) MIDIFILE;
    if (mf == NULL)
      return 0;
    i = mf->eventListIndex;
    j = mf->tempoListIndex;
    if (i >= mf->nEvents && j >= mf->nTempo) {
      /* there are no more events, */
      if ((unsigned long) csound->global_kcounter >= mf->totalKcnt &&
          !(csound->MTrkend)) {
        /* and end of file is reached: */
        csound->Message(csound, Str("end of midi track in '%s'\n"),
                                csound->oparms->FMidiname);
        csound->Message(csound, Str("%d forced decays, %d extra noteoffs\n"),
                                csound->Mforcdecs, csound->Mxtroffs);
        csound->MTrkend = 1;
        csoundMIDIFileClose(csound);
        csound->oparms->FMidiin = 0;
        if (csound->oparms->ringbell && !(csound->oparms->termifend))
          csound->Message(csound, "\a");
      }
      return 0;
    }
    /* otherwise read any events with time less than or equal to */
    /* current orchestra time */
    while (j < mf->nTempo &&
           (unsigned long) csound->global_kcounter >= mf->tempoList[j].kcnt) {
      /* tempo change */
      mf->currentTempo = mf->tempoList[j++].tempoVal;
    }
    mf->tempoListIndex = j;
    nRead = 0;
    while (i < mf->nEvents &&
           (unsigned long) csound->global_kcounter >= mf->eventList[i].kcnt) {
      n = msgDataBytes((int) mf->eventList[i].st) + 1;
      if (n < 1) {
        i++; continue;        /* unknown or system event: skip */
      }
      nBytes -= n;
      if (UNLIKELY(nBytes < 0)) {
        csound->Message(csound, Str(" *** buffer overflow while reading "
                                    "MIDI file events\n"));
        break;      /* return with whatever has been read so far */
      }
      nRead += n;
      *buf++ = mf->eventList[i].st;
      if (n > 1) *buf++ = mf->eventList[i].d1;
      if (n > 2) *buf++ = mf->eventList[i].d2;
      i++;
    }
    mf->eventListIndex = i;
    /* return the number of bytes read */
    return nRead;
}

/* destroy MIDI file event list */

int csoundMIDIFileClose(CSOUND *csound)
{
    /* nothing to do: memRESET() will free any allocated memory */
    MIDIFILE = (void*) NULL;
    return 0;
}

/* midirecv.c, resets MIDI controllers on a channel */
extern  void    midi_ctl_reset(CSOUND *csound, int16 chan);

/* called by csoundRewindScore() to reset performance to time zero */

void midifile_rewind_score(CSOUND *csound)
{
    int i;
    OPARMS *O = csound->oparms;

    if (MIDIFILE != NULL) {
      /* reset event index and tempo */
      MF(currentTempo) = default_tempo;
      MF(eventListIndex) = 0;
      MF(tempoListIndex) = 0;
      csound->MTrkend = csound->Mxtroffs = csound->Mforcdecs = 0;
      /* reset controllers on all channels */
      for (i = 0; i < MAXCHAN; i++)
        midi_ctl_reset(csound, (int16) i);
    } else if (LIKELY(O->FMidiname != NULL)) {
      csound->MTrkend = 0;
      if (UNLIKELY(csoundMIDIFileOpen(csound, O->FMidiname) != 0))
        csound->Die(csound, Str("Failed to load MIDI file."));
      O->FMidiin = 1;
    }
    else csound->Warning(csound, Str("Cannot rewind MIDI score\n"));
}

 /* ------------------------------------------------------------------------ */

/* miditempo opcode: returns the current tempo of MIDI file or score */

int midiTempoOpcode(CSOUND *csound, MIDITEMPO *p)
{
    if (MIDIFILE == NULL)
      *(p->kResult) = FL(60.0) *csound->esr / (MYFLT)(csound->ibeatTime);
    else
      *(p->kResult) = (MYFLT) MF(currentTempo);
    return OK;
}

int midiFileStatus(CSOUND *csound, MIDITEMPO *p){
  *p->kResult = csound->oparms->FMidiin;
  return OK;
}
