/*
 * MUS loader for DOSMid
 *
 * Copyright (C) 2014-2018 Mateusz Viste
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h> /* memcpy() */

#include "mem.h"
#include "midi.h"

#include "mus.h" /* include self for control */

#define TICKLEN 500000l /* length of a single MUS tick, in us */
#define TIMEUNITDIV 70  /* the MIDI time unit divisor */


/* loads a MUS file into memory, returns the id of the first event on success,
 * or -1 on error. channelsusage contains 16 flags indicating what channels
 * are used. */
long mus_load(FILE *fd, unsigned long *totlen, unsigned short *timeunitdiv, unsigned short *channelsusage) {
  unsigned char hdr_or_chanvol[16];
  unsigned short scorestart;
  int bytebuff, bytebuff2, loadflag = 0;
  unsigned long event_dtime, nextwait = 0;
  unsigned short event_type;
  unsigned short event_channel;
  long res = -1;
  int tickduration = 0;
  long mslen = 0;
  struct midi_event_t midievent;

  rewind(fd);
  /* read the 16 bytes header first, and populate hdr data */
  if (fread(hdr_or_chanvol, 1, 16, fd) != 16) return(-8);
  if ((hdr_or_chanvol[0] != 'M') || (hdr_or_chanvol[1] != 'U') || (hdr_or_chanvol[2] != 'S') || (hdr_or_chanvol[3] != 0x1A)) {
    return(-9);
  }
  scorestart = hdr_or_chanvol[6] | (hdr_or_chanvol[7] << 8);
  /* position the next reading position to first event */
  fseek(fd, scorestart, SEEK_SET);
  /* set tempo to 140 bpm (428571 us per quarter note) */
  memset(&midievent, 0, sizeof(struct midi_event_t));
  midievent.type = EVENT_TEMPO;
  *timeunitdiv = TIMEUNITDIV;
  midievent.data.tempoval = TICKLEN;
  if (pusheventqueue(&midievent, &res) != 0) return(MUS_OUTOFMEM);

  /* since now on, hdr_or_chanvol is used to store volume of channels */
  memset(hdr_or_chanvol, 0, 16);

  *channelsusage = 0; /* zero out the used instruments map */

  /* read events from the MUS file and translate them into midi events */
  for (;;) {
    bytebuff = fgetc(fd);
    if (bytebuff < 0) return(-5); /* if EOF, abort with error */
    event_channel = bytebuff & 0x0F;
    bytebuff >>= 4;
    event_type = bytebuff & 7;
    bytebuff >>= 3;
    event_dtime = bytebuff; /* if the 'last' bit is set, remember to read time after the event later */
    /* if channel is 15, it is percussion, and must be remapped to MIDI #9 */
    if (event_channel == 15) {
      event_channel = 9;
    } else if (event_channel == 9) {
      event_channel = 15;
    }
    /* clear out midievent to make room for the incoming MIDI message */
    memset(&midievent, 0, sizeof(struct midi_event_t));
    /* read complementary data, if any */
    switch (event_type) {
      case 0: /* release note (1 byte follows) */
        bytebuff = fgetc(fd);
        if ((bytebuff & 128) != 0) return(-13); /* MSB should always be zero */
        midievent.type = EVENT_NOTEOFF;
        midievent.data.note.note = bytebuff;
        midievent.data.note.chan = event_channel;
        midievent.data.note.velocity = 0;
        break;
      case 1: /* play note (1 or 2 bytes follow) */
        *channelsusage |= (1 << event_channel); /* update the channel usage flags */
        bytebuff = fgetc(fd);
        midievent.type = EVENT_NOTEON;
        midievent.data.note.note = bytebuff & 127;
        midievent.data.note.chan = event_channel;
        if ((bytebuff & 128) != 0) {
          midievent.data.note.velocity = fgetc(fd);
          hdr_or_chanvol[event_channel] = midievent.data.note.velocity; /* remember the last velocity, might be needed later */
        } else {
          midievent.data.note.velocity = hdr_or_chanvol[event_channel];
        }
        break;
      case 2: /* pitch wheel (1 byte follows) */
        /* MIDI says that pitch wheel is a 14bit value with the center being
         * at 0x2000. MUS on the other hand provides only 8bits, so some
         * adjustement must be made as to fit into this scheme:
         *   0  =  two half-tones down
         *  64  =  one half-tone down
         * 128  =  normal (default)
         * 192  =  one half-tone up
         * 255  =  two half-tones up  */
        bytebuff = fgetc(fd);
        midievent.type = EVENT_PITCH;
        midievent.data.pitch.wheel = bytebuff << 6; /* convert wheel value to 14 bits as expected by MIDI */
        midievent.data.pitch.chan = event_channel;
        break;
      case 3: /* sysex (1 byte follows) - I ignore SYSEX messages */
        bytebuff = fgetc(fd);
        if ((bytebuff & 128) != 0) return(-11); /* MSB should always be zero */
        break;
      case 4: /* control (2 bytes follow) */
        bytebuff = fgetc(fd);
        bytebuff2 = fgetc(fd);
        if ((bytebuff == 0) && (bytebuff2 < 128)) { /* change program */
          midievent.type = EVENT_PROGCHAN;
          midievent.data.prog.prog = bytebuff2;
          midievent.data.note.chan = event_channel;
        } else if ((bytebuff >= 1) && (bytebuff <= 9)) { /* else it maps directly to a MIDI controller message */
          int tmpmap[10] = {0,0,1,7,10,11,91,93,64,67}; /* map MUS controllers to MIDI controller IDs */
          midievent.type = EVENT_CONTROL;
          midievent.data.control.chan = event_channel;
          midievent.data.control.id = tmpmap[bytebuff];
          midievent.data.control.val = bytebuff2;
        } else { /* else it's an illegate byte pattern - abort mission, captain! */
          return(-91);
        }
        break;
      case 6: /* end of song (no byte follow) */
        if (pusheventqueue(NULL, NULL) != 0) return(MUS_OUTOFMEM);
        loadflag = 1;
        break;
      default: /* unknown event type - abort */
        return(-3);
        break;
    }
    /* if file loaded fine, break out of the loop now */
    if (loadflag != 0) break;
    /* fill in the delta time */
    midievent.deltatime = nextwait;
    /* if dtime is non-zero, read the number of ticks to wait before next note */
    nextwait = 0;
    while (event_dtime != 0) {
      bytebuff = fgetc(fd);
      if (bytebuff < 0) return(-6);
      event_dtime = bytebuff & 128;
      nextwait <<= 7;
      nextwait |= (bytebuff & 127);
    }
    /* push the event into memory */
    if (pusheventqueue(&midievent, NULL) != 0) return(MUS_OUTOFMEM);
    /* recompute total song's length */
    tickduration += nextwait;
    while (tickduration >= TIMEUNITDIV) {
      mslen += TICKLEN / 1000; /* mslen is in miliseconds, while TICKLEN is in us */
      tickduration -= TIMEUNITDIV;
    }
  }
  mslen += (tickduration / TIMEUNITDIV * TICKLEN) / 1000;
  *totlen = mslen / 1000; /* totlen is in seconds */
  /* */
  return(res);
}
