/* midiplay - plays MIDI file through PC internal speaker
 * 
 * version 1.1
 *
 * written by James Allwright 27th March 1997
 * Department of Electronics and Computer Science,
 * University of Southampton, UK
 * Comments to jra@ecs.soton.ac.uk
 *
 * karaoke capability added October 1998.
 * 
 * based on public domain 'midifilelib' package.
 *
 * This code may be freely distributed.
 */

#include <stdio.h>
#include <math.h>
#include "midifile.h"

static FILE *F;
int SECONDS;    /* global that tells whether to display seconds or ticks */
int division;    /* from the file header */
long tempo = 500000; /* the default tempo is 120 beats/minute */
int unitlen;
int channel;
int track, trackno, settrack;
int karaokeon;
int format;
int active[256];
int transpose;
int tempofactor;
extern char* index();
#define MAXSYLL 20

struct note_type {
  long start, stop;
  int pitch;
  char  text[MAXSYLL]; /* should be enough for most syllables */
  struct note_type *next;
};

struct note_type *head;
struct note_type *tail;
struct note_type *textplace;

struct note_type* create(my_pitch, now)
int my_pitch;
long now;
{
  extern char* malloc();
  struct note_type* newitem;

  newitem = (struct note_type*) malloc(sizeof(struct note_type));
  if (newitem == NULL) {
    printf("Could not allocate memory for note\n");
    exit(0);
  };
  newitem->next = NULL;
  newitem->start = now;
  newitem->pitch = my_pitch;
  newitem->text[0] = '\0'; /* empty string */
  return(newitem);
};

change(newpitch, time)
int newpitch;
long time;
{
  if (time == tail->start) {
    tail->pitch = newpitch;
  } else {
    tail->stop = time;
    tail->next = create(newpitch, time);
    tail = tail->next;
  };
};

addwords(p, s)
struct note_type* p;
char* s;
{
  char *linebreak;

  if ((strlen(s) + strlen(p->text)) < MAXSYLL) {
    strcpy(p->text + strlen(p->text), s);
    linebreak = index(p->text, '\\');
    /* use only one linebreak marker for simplicity */
    if (linebreak != NULL) {
      *linebreak = '/';
    };
  } else {
    /* assume any long strings are not part of the karaoke words */
    printf("- %s\n", s);
  };
};

struct note_type* findtextplace(p, now)
/* find place in list for karaoke syllables words to go */
/* p is old value for textplace, normally just before now */
struct note_type* p;
long now; /* time */
{
  struct note_type* q;

  q = p;
  if ((q == NULL) || (q->start > now)) {
    q = head;
  };
  while ((q != NULL) && (q->start < now)) {
    q = q->next;
  };
  return(q);
}

dotext(place)
struct note_type* place;
{
  struct note_type* t;
  char *c;
  char *q;
  
  c = index(place->text, '/');
  if (c != NULL) {
   /* print out whole line in advance */
    c++;
    printf("\r\"%s", c);
    t = place->next;
    while ((t != NULL) && (index(t->text, '/') == NULL)) {
      printf(t->text);
      t = t->next;
    };
    if (t != NULL) {
      q = t->text;
      while (*q != '/') {
        printf("%c", *q);
        q++;
      };
    };
    printf("\"\n %s", c);
  } else {
    printf(place->text);
  };
}

filegetc()
{
    return(getc(F));
}

/* for crack */
extern int arg_index;

int readnum(num) 
char *num;
{
  int t;
  char *p;
  
  t = 0;
  p = num;
  while (((int)*p >= '0') && ((int)*p <= '9')) {
    t = t * 10 + (int) *p - '0';
    p = p + 1;
  };
  return t;
};

int readnump(p) 
char **p;
{
  int t;
  
  t = 0;
  while (((int)**p >= '0') && ((int)**p <= '9')) {
    t = t * 10 + (int) **p - '0';
    *p = *p + 1;
  };
  return t;
};

int getarg(option, argc, argv)
char *option;
char *argv[];
int argc;
{
  int j, place;

  place = -1;
  for (j=0; j<argc; j++) {
    if (strcmp(option, argv[j]) == 0) {
      place = j + 1;
    };
  };
  return (place);
};

main(argc,argv)
char *argv[];
int argc;
{
  FILE *efopen();
  void note();
  void clearkey();
  void initfuncs();
  float mf_ticks2sec();
  int j;
  int arg;
  int i, interval[256];
  double conv, freq;
  struct note_type *place;

  /* set up array of delay times corresponding to notes */
  conv = log((double) 2);
  for (i=0; i<256; i++) {
    freq = (double) exp((double) (conv * (i-69))/12.0) * 440;
    interval[i] = (int) (((double) 1193180)/freq + (double) 0.5);
  };

  /* initialize linked list */
  head = create(-1, (long) 0);
  textplace = NULL;
  tail = head;
  for (i=0; i<256; i++) {
    active[i] = 0;
  };
  karaokeon = 0;

  arg = getarg("-i", argc, argv);
  if (arg != -1) {
    printf("\n     Midiplay\n");
    printf("     --------\n");
    printf("written March 1997 by James Allwright\n");
    printf("using the midifilelib MIDI library.\n");
    printf("Karaoke capability added October 1998.\n\n");
    printf("This program plays a MIDI file through the PC's internal\n");
    printf("speaker. The output is monophonic, so some notes may be lost.\n");
    printf("Where two or more notes should be played simultaneously,\n");
    printf("midiplay plays the highest pitch one. If the MIDI file is\n");
    printf("type 1, midiplay chooses the first non-empty track to play\n");
    printf("unless some other track has been specified with the -t option.\n");
    printf("While a file is playing, press any key to stop it.\n");
    exit(0);
  };
  arg = 1;
  if (arg < argc) {
    F = efopen(argv[arg],"rb");
  } else {
    printf("midiplay version 1.1\n  usage :\n");
    printf("midiplay midifile <options>\n");
    printf("         -t <track>\n");
    printf("         -c <channel>\n");
    printf("         -s <semitones to transpose>\n");
    printf("         -p <percentage of original speed> 100 = no change\n");
    printf("         -i more information\n");
    exit(0);
  };
  arg = getarg("-t", argc, argv);
  if ((arg != -1) && (arg < argc)) {
    track = readnum(argv[arg]);
    settrack = 1;
  } else {
    track = 0;
    settrack = 0;
  };
  arg = getarg("-p", argc, argv);
  if ((arg != -1) && (arg < argc)) {
    tempofactor = readnum(argv[arg]) - 1;
  } else {
    tempofactor = 100;
  };
  arg = getarg("-c", argc, argv);
  if ((arg != -1) && (arg < argc)) {
    channel = readnum(argv[arg]) - 1;
  } else {
    channel = -1;
  };
  transpose = 0;
  arg = getarg("-s", argc, argv);
  if ((arg != -1) && (arg < argc)) {
    int t;

    if (*argv[arg] == '-') {
      transpose = - readnum(argv[arg] + 1);
    } else {
      transpose = readnum(argv[arg]);
    };
  };
  trackno = 0;
  initfuncs();
  Mf_getc = filegetc;
  mfread();
  fclose(F);
  /* finish off tune */
  tail->stop = Mf_currtime;
  /* post-process tune */
  place = head;
  while (place != NULL) {
    long dt;

    if (place->stop > place->start) {
      dt = place->stop - place->start;
      place->start = (long) (1000000.0 * mf_ticks2sec(dt, division, tempo)
                                       * ((float) tempofactor/100.0));
    } else {
     place->start = (long) 0;
    };
    if (place->pitch != -1) {
      place->pitch = interval[place->pitch];
    };
    place = place->next;
  };
  /* play tune */
  place = head;
  while (place != NULL) {
    dotext(place);
    note(place->pitch, place->start);
    place = place->next;
  };
  clearkey();
  /* free up space */
  place = head;
  while (place != NULL) {
    tail = place->next;
    free(place);
    place = tail;
  };
};

FILE *
efopen(name,mode)
char *name;
char *mode;
{
    FILE *f;
/*
    extern int errno;
    extern char *sys_errlist[];
    extern int sys_nerr;
    char *errmess;
*/

    if ( (f=fopen(name,mode)) == NULL ) {
        (void) fprintf(stderr,"*** ERROR *** Cannot open '%s'!\n",name);
/*
        if ( errno <= sys_nerr )
            errmess = sys_errlist[errno];
        else
            errmess = "Unknown error!";
        (void) fprintf(stderr,"************* Reason: %s\n",errmess);
*/
        exit(1);
    }
    return(f);
}

error(s)
char *s;
{
    fprintf(stderr,"Error: %s\n",s);
}

txt_header(xformat,ntrks,ldivision)
int xformat, ntrks, ldivision;
{
  division = ldivision; 
  format = xformat;
}

txt_trackstart()
{
  trackno = trackno + 1;
  /* read from first non-empty track */
  if ((settrack == 0) && (head == tail)) {
    track = trackno;
  };
}

txt_trackend()
{
  karaokeon = 0;
}

int trackfilter()
{
   return ((track == 0) || (track == trackno));
};

int chantrackfilter(chan)
int chan;
{
  return (((channel == -1) || (channel == chan)) &&
          ((track == 0) || (track == trackno)));
};

do_on(pitch)
int pitch;
{
  active[pitch] = 1;
  if (pitch > tail->pitch) {
    change(pitch, Mf_currtime);
  };
};

do_off(pitch)
int pitch;
{
  int i, nextpitch;

  active[pitch] = 0;
  nextpitch = -1;
  i = 255;
  while ((i>-1) && (nextpitch == -1)) {
    if (active[i] == 1) {
      nextpitch = i;
    };
    i = i - 1;
  };
  change(nextpitch, Mf_currtime);
};

int clip(x)
int x;
{
  int y;

  y = x;
  if (y > 255) y = 255; 
  if (y < 0)   y = 0;
  return(y);
};

txt_noteon(chan,pitch,vol)
int chan, pitch, vol;
{
  if (chantrackfilter(chan)) {
    if (vol != 0) {
      do_on(clip(pitch+transpose));
    } else {
      do_off(clip(pitch+transpose));
    };
  };
}

txt_noteoff(chan,pitch,vol)
int chan, pitch, vol;
{
  if (chantrackfilter(chan)) {
    do_off(clip(pitch+transpose));
  };
}

txt_pressure(chan,pitch,press)
int chan, pitch, press;
{
}

txt_parameter(chan,control,value)
int chan, control, value;
{
}

txt_pitchbend(chan,msb,lsb)
int chan, msb, lsb;
{
}

txt_program(chan,program)
int chan, program;
{
}

txt_chanpressure(chan,press)
int chan, press;
{
}

txt_sysex(leng,mess)
int leng;
char *mess;
{
}

txt_metamisc(type,leng,mess)
int type, leng;
char *mess;
{
}

txt_metaspecial(type,leng,mess)
int type, leng;
char *mess;
{
}

txt_metatext(type,leng,mess)
int type, leng;
char *mess;
{ 
  char buffer[40];
  int i, l;

  l = leng;
  if (l>39) {
    l = 39;
  };
  for (i=0; i<l; i++) {
    buffer[i] = mess[i];
  };
  buffer [l] = '\0';
  if (buffer[0] == '@') {
    if (buffer[1] == 'T') {
      printf("%s\n\n", &buffer[2]);
    };
    karaokeon = 1;
  } else {
    if (karaokeon) {
      textplace = findtextplace(textplace, Mf_currtime);
      addwords(textplace, buffer);
    };
  };
}

txt_metaseq(num)
int num;
{  
}

txt_metaeot()
{
}

txt_keysig(sf,mi)
char sf, mi;
{
}

txt_tempo(ltempo)
long ltempo;
{
    tempo = ltempo;
}

txt_timesig(nn,dd,cc,bb)
int nn, dd, cc, bb;
{
}

txt_smpte(hr,mn,se,fr,ff)
int hr, mn, se, fr, ff;
{
}

txt_arbitrary(leng,mess)
int leng;
char *mess;
{
}

void initfuncs()
{
    Mf_error = error;
    Mf_header =  txt_header;
    Mf_trackstart =  txt_trackstart;
    Mf_trackend =  txt_trackend;
    Mf_noteon =  txt_noteon;
    Mf_noteoff =  txt_noteoff;
    Mf_pressure =  txt_pressure;
    Mf_parameter =  txt_parameter;
    Mf_pitchbend =  txt_pitchbend;
    Mf_program =  txt_program;
    Mf_chanpressure =  txt_chanpressure;
    Mf_sysex =  txt_sysex;
    Mf_metamisc =  txt_metamisc;
    Mf_seqnum =  txt_metaseq;
    Mf_eot =  txt_metaeot;
    Mf_timesig =  txt_timesig;
    Mf_smpte =  txt_smpte;
    Mf_tempo =  txt_tempo;
    Mf_keysig =  txt_keysig;
    Mf_seqspecific =  txt_metaspecial;
    Mf_text =  txt_metatext;
    Mf_arbitrary =  txt_arbitrary;
}
