/*
 * abc2midi - program to convert abc files to MIDI files.
 * Copyright (C) 1999 James Allwright
 * e-mail: J.R.Allwright@westminster.ac.uk
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

/* store.c  
 * This file handles all event_X() calls from parseabc.c and stores them 
 * in various arrays and data structures. The midi is then generated by
 * calling mfwrite() which in turn uses the routine writetrack().
 * This file is part of abc2midi.
 *
 * James Allwright
 *
 * Macintosh Port 30th July 1996
 * Wil Macaulay (wil@syndesis.com)
 */

#include "abc.h"
#include "parseabc.h"
#include "parser2.h"
#include "midifile.h"
#include "genmidi.h"
#include <stdio.h>

#ifdef __MWERKS__
#define __MACINTOSH__ 1
#endif /* __MWERKS__ */

#ifdef __MACINTOSH__
int setOutFileCreator(char *fileName,unsigned long theType,
                      unsigned long theCreator);
#endif /* __MACINTOSH__ */
/* define USE_INDEX if your C libraries have index() instead of strchr() */
#ifdef USE_INDEX
#define strchr index
#endif

#ifdef ANSILIBS
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#else
extern char* strchr();
extern void reduce();
#endif

#define MAXLINE 500
#define INITTEXTS 20
#define INITWORDS 20
#define MAXCHANS 16

/* global variables grouped roughly by function */

FILE *fp;

/* parsing stage */
int tuplecount, tfact_num, tfact_denom, tnote_num, tnote_denom;
int specialtuple;
int gracenotes;
int headerpartlabel;
int dotune, pastheader;
int hornpipe, last_num, last_denom;
int timesigset;
int retain_accidentals;
int ratio_a, ratio_b;

struct voicecontext {
  /* maps of accidentals for each stave line */
  char basemap[7], workmap[7];
  int  basemul[7], workmul[7];
  int default_length;
  int voiceno;
  int indexno;
  int hasgchords;
  int haswords;
  int inslur;
  int ingrace;
  int octaveshift;
  /* chord handling */
  int inchord, chordcount;
  int chord_num, chord_denom;
  /* details of last 2 notes/chords to apply length-modifiers to */
  int laststart, lastend, thisstart, thisend;
  /* broken rythm handling */
  int brokentype, brokenmult, brokenpending;
  int broken_stack[7];
  struct voicecontext* next;
};
struct voicecontext global;
struct voicecontext* v;
struct voicecontext* head;
int voicecount;

/* storage structure for strings */
int maxtexts = INITTEXTS;
char** atext;
int ntexts = 0;

/* Named guitar chords */
char chordname[MAXCHORDNAMES][8];
int chordnotes[MAXCHORDNAMES][6];
int chordlen[MAXCHORDNAMES];
int chordsnamed = 0;

/* general purpose storage structure */
int maxnotes;
int *pitch, *num, *denom;
featuretype *feature;
int notes;

int verbose = 0;
int xmatch;
int sf, mi;
int gchordvoice, wordvoice, drumvoice;
int gchordtrack, drumtrack;

/* Part handling */
struct vstring part;
extern int parts, partno, partlabel;
extern int part_start[26], part_count[26];

int voicesused;

/* Tempo handling (Q: field) */
int time_num, time_denom;
long tempo;
int tempo_num, tempo_denom;
int relative_tempo, Qtempo;
extern int division;
extern int div_factor;

/* output file generation */
int userfilename = 0;
char *outname = NULL;
char *outbase = NULL;
int check;
int ntracks;

/* bar length checking */
extern int bar_num, bar_denom;
int barchecking;

/* generating MIDI output */
int middle_c;
extern int channels[MAXCHANS + 3];
extern int global_transpose;
extern int additive;
int gfact_num, gfact_denom;

/* karaoke handling */
int karaoke, wcount;
char** words;
int maxwords = INITWORDS;

extern int* checkmalloc();
extern long writetrack();

char* addstring(s)
/* create space for string and store it in memory */
char* s;
{
  char* p;

  p = (char*) checkmalloc(strlen(s)+1);
  strcpy(p, s);
  return(p);
}

static struct voicecontext* newvoice(n)
/* allocate and initialize the data for a new voice */
int n;
{
  struct voicecontext *s;
  int i;

  s = (struct voicecontext*) checkmalloc(sizeof(struct voicecontext));
  voicecount = voicecount + 1;
  s->voiceno = n;
  s->indexno = voicecount;
  s->default_length = global.default_length;
  s->hasgchords = 0;
  s->haswords = 0;
  s->inslur = 0;
  s->ingrace = 0;
  s->inchord = 0;
  s->chordcount = 0;
  s->laststart = -1;
  s->lastend = -1;
  s->thisstart = -1;
  s->thisend = -1;
  s->brokenpending = -1;
  s->next = NULL;
  for (i=0; i<7; i++) {
    s->basemap[i] = global.basemap[i];
    s->basemul[i] = global.basemul[i];
    s->workmap[i] = global.workmap[i];
    s->workmul[i] = global.workmul[i];
  };
  s->octaveshift = global.octaveshift;
  return(s);
}

static struct voicecontext* getvoicecontext(n)
/* find the data structure for a given voice number */
int n;
{
  struct voicecontext *p;
  struct voicecontext *q;

  p = head;
  q = NULL;
  while ((p != NULL) && (p->voiceno != n)) {
    q = p;
    p = p->next;
  };
  if (p == NULL) {
    p = newvoice(n);
    if (q != NULL) {
      q->next = p;
    };
  };
  if (head == NULL) {
    head = p;
  };
  return(p);
}

static void clearvoicecontexts()
/* free up all the memory allocated to voices */
{
  struct voicecontext *p;
  struct voicecontext *q;

  p = head;
  while (p != NULL) {
    q = p->next;
    free(p);
    p = q;
  };
  head = NULL;
}

static int getchordnumber(s)
/* looks through list of known chords for chord name given in s */
char *s;
{
  int i;
  int chordnumber;

  chordnumber = 0;
  i = 1;
  while ((i <= chordsnamed) && (chordnumber == 0)) {
    if (strcmp(s, chordname[i]) == 0) {
      chordnumber = i;
    } else {
      i = i + 1;
    };
  };
  return(chordnumber);
}

static void addchordname(s, len, notes)
/* adds chord name and note set to list of known chords */
char *s;
int notes[];
int len;
{
  int i, j, done;

  if (strlen(s) > 7) {
    event_error("Chord name cannot exceed 7 characters");
    return;
  };  
  if (len > 6) {
    event_error("Named chord cannot have more than 6 notes");
    return;
  };
  i = 0;
  done = 0;
  while ((i<chordsnamed) && (!done)) {
    if (strcmp(s, chordname[i]) == 0) {
      /* change chord */
      chordlen[i] = len;
      for (j=0; j<len; j++) {
        chordnotes[i][j] = notes[j];
      };
      done = 1;
    } else {
      i = i + 1;
    };
  };
  if (!done) {
    if (chordsnamed >= MAXCHORDNAMES-1) {
      event_error("Too many Guitar Chord Names used");
    } else {
      chordsnamed = chordsnamed + 1;
      strcpy(chordname[chordsnamed], s);
      chordlen[chordsnamed] = len;
      for (j=0; j<len; j++) {
        chordnotes[chordsnamed][j] = notes[j];
      };
    };
  };
}

static void setup_chordnames()
/* set up named guitar chords */
{
  static int list_Maj[3] = {0, 4, 7};
  static int list_m[3] = {0, 3, 7};
  static int list_7[4] = {0, 4, 7, 10};
  static int list_m7[4] = {0, 3, 7, 10};
  static int list_maj7[4] = {0, 4, 7, 11};
  static int list_M7[4] = {0, 4, 7, 11};
  static int list_6[4] = {0, 4, 7, 9};
  static int list_m6[4] = {0, 3, 7, 9};
  static int list_aug[3] = {0, 4, 8};
  static int list_plus[3] = {0, 4, 8};
  static int list_aug7[4] = {0, 4, 8, 10};
  static int list_dim[3] = {0, 3, 6};
  static int list_dim7[4] = {0, 3, 6, 9};
  static int list_9[5] = {0, 4, 7, 10, 2};
  static int list_m9[5] = {0, 3, 7, 10, 2};
  static int list_maj9[5] = {0, 4, 7, 11, 2};
  static int list_M9[5] = {0, 4, 7, 11, 2};
  static int list_11[6] = {0, 4, 7, 10, 2, 5};
  static int list_dim9[5] = {0, 4, 7, 10, 13};
  static int list_sus[3] = {0, 5, 7};
  static int list_sus9[3] = {0, 2, 7};
  static int list_7sus4[4] = {0, 5, 7, 10};
  static int list_7sus9[4] = {0, 2, 7, 10};
  static int list_5[2] = {0, 7};
  
  addchordname("", 3, list_Maj);
  addchordname("m", 3, list_m);
  addchordname("7", 4, list_7);
  addchordname("m7", 4, list_m7);
  addchordname("maj7", 4, list_maj7);
  addchordname("M7", 4, list_M7);
  addchordname("6", 4, list_6);
  addchordname("m6", 4, list_m6);
  addchordname("aug", 3, list_aug);
  addchordname("+", 3, list_plus);
  addchordname("aug7", 4, list_aug7);
  addchordname("dim", 3, list_dim);
  addchordname("dim7", 4, list_dim7);
  addchordname("9", 5, list_9);
  addchordname("m9", 5, list_m9);
  addchordname("maj9", 5, list_maj9);
  addchordname("M9", 5, list_M9);
  addchordname("11", 6, list_11);
  addchordname("dim9", 5, list_dim9);
  addchordname("sus", 3, list_sus);
  addchordname("sus9", 3, list_sus9);
  addchordname("7sus4", 4, list_7sus4);
  addchordname("7sus9", 4, list_7sus9);
  addchordname("5", 2, list_5);
}

void event_init(argc, argv, filename)
/* this routine is called first by parseabc.c */
int argc;
char* argv[];
char **filename;
{
  int j;

  /* look for code checking option */
  if (getarg("-c", argc, argv) != -1) {
    check = 1;
  } else {
    check = 0;
  };
  /* look for verbose option */
  if (getarg("-v", argc, argv) != -1) {
    verbose = 1;
  } else {
    verbose = 0;
  };
  maxnotes = 500;
  /* allocate space for notes */
  pitch = checkmalloc(maxnotes*sizeof(int));
  num = checkmalloc(maxnotes*sizeof(int));
  denom = checkmalloc(maxnotes*sizeof(int));
  feature = (featuretype*) checkmalloc(maxnotes*sizeof(featuretype));
  /* and for text */
  atext = (char**) checkmalloc(maxtexts*sizeof(char*));
  words = (char**) checkmalloc(maxwords*sizeof(char*));
  if ((getarg("-h", argc, argv) != -1) || (argc < 2)) {
    printf("abc2midi version 1.11\n");
    printf("Usage : abc2midi <abc file> [reference number] [-c] [-v] ");
    printf("[-o filename]\n");
    printf("        [reference number] selects a tune\n");
    printf("        -c selects checking only\n");
    printf("        -v selects verbose option\n");
    printf("        -o filename\n");
    printf(" The default action is to write a MIDI file for each abc tune\n");
    printf(" with the filename <stem>N.mid, where <stem> is the filestem\n");
    printf(" of the abc file and N is the tune reference number. If the -o\n");
    printf(" option is used, only one file is written. This is the tune\n");
    printf(" specified by the reference number or, if no reference number\n");
    printf(" is given, the first tune in the file.\n");
    exit(0);
  } else {
    xmatch = 0;
    if ((argc >= 3) && (isdigit(*argv[2]))) {
      xmatch = readnumf(argv[2]);
    };
    *filename = argv[1];
    outbase = addstring(argv[1]);
    for (j = 0; j<strlen(outbase); j++) {
      if (outbase[j] == '.') outbase[j] = '\0';
    };
  };
  /* look for user-supplied output filename */
  j = getarg("-o", argc, argv);
  if (j != -1) {
    if (argc >= j+1) {
      outname = addstring(argv[j]);
      userfilename = 1;
      if (xmatch == 0) {
        xmatch = -1;
      };
    } else {
      event_error("No filename given, ignoring -o option");
    };
  };
  dotune = 0;
  parseroff();
  setup_chordnames();
}

void event_text(s)
/* text found in abc file */
char *s;
{
  char msg[200];

  sprintf(msg, "Ignoring text: %s", s);
  event_warning(msg);
}

void event_reserved(p)
/* reserved character H-Z found in abc file */
char p;
{
  char msg[200];

  sprintf(msg, "Ignoring reserved character %c", p);
  event_warning(msg);
}

void event_tex(s)
/* TeX command found - ignore it */
char *s;
{
}

void event_fatal_error(s)
/* print error message and halt */
char *s;
{
  event_error(s);
  exit(1);
}

void event_error(s)
/* generic error handler */
char *s;
{
#ifdef NOFTELL
  extern int nullpass;

  if (nullpass != 1) {
    printf("Error in line %d : %s\n", lineno, s);
  };
#else
  printf("Error in line %d : %s\n", lineno, s);
#endif
}

void event_warning(s)
/* generic warning handler - for flagging possible errors */
char *s;
{
#ifdef NOFTELL
  extern int nullpass;

  if (nullpass != 1) {
    printf("Warning in line %d : %s\n", lineno, s);
  };
#else
  printf("Warning in line %d : %s\n", lineno, s);
#endif
}

static int autoextend(maxnotes)
/* increase the number of abc elements the program can cope with */
int maxnotes;
{
  int newlimit;
  int *ptr;
  featuretype *fptr;
  int i;

  if (verbose) {
    event_warning("Extending note capacity");
  };
  newlimit = maxnotes*2;
  fptr = (featuretype*) checkmalloc(newlimit*sizeof(featuretype));
  for(i=0;i<maxnotes;i++){
    fptr[i] = feature[i];
  };
  free(feature);
  feature = fptr;
  ptr = checkmalloc(newlimit*sizeof(int));
  for(i=0;i<maxnotes;i++){
    ptr[i] = pitch[i];
  };
  free(pitch);
  pitch = ptr;
  ptr = checkmalloc(newlimit*sizeof(int));
  for(i=0;i<maxnotes;i++){
    ptr[i] = num[i];
  };
  free(num);
  num = ptr;
  ptr = checkmalloc(newlimit*sizeof(int));
  for(i=0;i<maxnotes;i++){
    ptr[i] = denom[i];
  };
  free(denom);
  denom = ptr;
  return(newlimit);
}

static int textextend(maxstrings, stringarray)
/* resize an array of pointers to strings */
/* used with arrays words and atext */
int maxstrings;
char*** stringarray;
{
  int i, newlimit;
  char** ptr;

  newlimit = maxstrings*2;
  if (verbose) {
    event_warning("Extending text capacity");
  };
  ptr = (char**) checkmalloc(newlimit*sizeof(char*));
  for(i=0;i<maxstrings;i++){
    ptr[i] = (*stringarray)[i];
  };
  free(*stringarray);
  *stringarray = ptr;
  return(newlimit);
}

static void addfeature(f, p, n, d)
/* place feature in internal table */
int f, p, n, d;
{
  feature[notes] = f;
  pitch[notes] = p;
  num[notes] = n;
  denom[notes] = d;
  if ((f == NOTE) || (f == REST) || (f == CHORDOFF)) {
    reduce(&num[notes], &denom[notes]);
  };
  notes = notes + 1;
  if (notes >= maxnotes) {
    maxnotes = autoextend(maxnotes);
  };
}

void event_linebreak()
/* reached end of line in abc */
{
  addfeature(LINENUM, lineno, 0, 0);
}

void event_startmusicline()
/* starting to parse line of abc music */
{
  addfeature(MUSICLINE, 0, 0, 0);
}

void event_endmusicline(endchar)
/* finished parsing line of abc music */
char endchar;
{
  addfeature(MUSICSTOP, 0, 0, 0);
}

static void textfeature(type, s)
/* called while parsing abc - stores an item which requires an */
/* associared string */
int type;
char* s;
{
  atext[ntexts] = addstring(s);
  addfeature(type, ntexts, 0, 0);
  ntexts = ntexts + 1;
  if (ntexts >= maxtexts) {
    maxtexts = textextend(maxtexts, &atext);
  };
}

void event_comment(s)
/* comment found in abc */
char *s;
{
  if (dotune) {
    if (pastheader) {
      textfeature(TEXT, s);
    } else {
      textfeature(TEXT, s);
    };
  };
}

void event_specific(package, s)
/* package-specific command found i.e. %%NAME */
/* only %%MIDI commands are actually handled */
char *package, *s;
{
  char msg[200], command[40];
  char *p;

  if (strcmp(package, "MIDI") == 0) {
    int ch;
    int done;
    int trans, rtrans;

    p = s;
    done = 0;
    skipspace(&p);
    readstr(command, &p);
    if (strcmp(command, "channel") == 0) {
      skipspace(&p);
      ch = readnump(&p) - 1;
      addfeature(CHANNEL, ch, 0, 0);
      done = 1;
    };
    trans = strcmp(command, "transpose");
    rtrans = strcmp(command, "rtranspose");
    if ((trans == 0) || (rtrans == 0)) {
      int neg, val;

      skipspace(&p);
      neg = 0;
      if (*p == '+') p = p + 1;
      if (*p == '-') {
        p = p + 1;
        neg = 1;
      };
      skipspace(&p);
      val = readnump(&p);
      if (neg) val = - val;
      if (pastheader) {
        if (trans == 0) {
          addfeature(TRANSPOSE, val, 0, 0);
        } else {
          addfeature(RTRANSPOSE, val, 0, 0);
        };
      } else {
        if (trans == 0) {
          global_transpose = val;
        } else {
          global_transpose = global_transpose + val;
        };
      };
      done = 1;
    };
    if (strcmp(command, "C") == 0) {
      int val;

      skipspace(&p);
      val = readnump(&p);
      middle_c = val;
      done = 1;
    };
    if (strcmp(command, "nobarlines") == 0) {
      retain_accidentals = 0;
      done = 1;
    };
    if (strcmp(command, "barlines") == 0) {
      retain_accidentals = 1;
      done = 1;
    };
    if (strcmp(command, "ratio") == 0) {
      int a, b;

      skipspace(&p);
      b = readnump(&p);
      skipspace(&p);
      a = readnump(&p);
      if ((a > 0) && (b > 0)) {
        ratio_a = a;
        ratio_b = b;
        if (ratio_a + ratio_b % 2 == 1) {
          ratio_a = 2 * a;
          ratio_b = 2 * b;
        };
      } else {
        event_error("Invalid ratio");
      };
      done = 1;
    };
    if (strcmp(command, "grace") == 0) {
      int a, b;
      char msg[40];

      skipspace(&p);
      a = readnump(&p);
      if (*p != '/') {
        event_error("Need / in MIDI grace command");
      } else {
        p = p + 1;
      };
      b = readnump(&p);
      if ((a < 1) || (b < 1) || (a >= b)) {
        sprintf(msg, "%d/%d is not a suitable fraction", a, b);
        event_error(msg);
      } else {
        if (pastheader) {
          addfeature(SETGRACE, a, 0, b);
        } else {
          gfact_num = a;
          gfact_denom = b;
        };
      };
      done = 1;
    };
    if (strcmp(command, "gchordon") == 0) {
      addfeature(GCHORDON, 0, 0, 0);
      done = 1;
    };
    if (strcmp(command, "gchordoff") == 0) {
      addfeature(GCHORDOFF, 0, 0, 0);
      done = 1;
    };
    if (strcmp(command, "chordname") == 0) {
      char name[20];
      int i, notes[6];

      skipspace(&p);
      i = 0;
      while ((i<19) && (*p != ' ') && (*p != '\0')) {
        name[i] = *p;
        p = p + 1;
        i = i + 1;
      };
      name[i] = '\0';
      if (*p != ' ') {
        event_error("Bad format for chordname command");
      } else {
        i = 0;
        while ((i<=6) && (*p == ' ')) {
          skipspace(&p);
          notes[i] = readnump(&p);
          i = i + 1;
        };
        addchordname(name, i, notes);
      };
      done = 1;
    };
    if (done == 0) {
      /* add as a command to be interpreted later */
      textfeature(DYNAMIC, s);
    };
  } else {
    strcpy(msg, "%");
    strcat(msg, package);
    strcat(msg, s);
    event_comment(msg);
  };
}

void event_startinline()
/* start of in-line field in abc music line */
{
}

void event_closeinline()
/* end of in-line field in abc music line */
{
}

void event_field(k, f)
/* Handles R: T: and any other field not handled elsewhere */
char k;
char *f;
{
  if (dotune) {
    switch (k) {
    case 'T':
      textfeature(TITLE, f);
      break;
    case 'R':
      {
        char* p;

        p = f;
        skipspace(&p);
        if ((strncmp(p, "Hornpipe", 8) == 0) ||
            (strncmp(p, "hornpipe", 8) == 0)) {
          hornpipe = 1;
        };
      };
      break;
    default:
      {
        char buff[100];

        if (strlen(f) < 98) {
          sprintf(buff, "%c:%s", k, f);
          textfeature(TEXT, buff);
        };
      };
    };
  };
}

void event_words(p, continuation)
/* handles a w: field in the abc */
char* p;
int continuation;
{
  int l;

  karaoke = 1;
  v->haswords = 1;
  if ((wordvoice != 0) && (wordvoice != v->indexno)) {
    event_warning("More than one voice with words in");
  };
  wordvoice = v->indexno;
  words[wcount] = addstring(p);
  addfeature(WORDLINE, wcount, 0, 0);
  if (continuation == 0) {
    addfeature(WORDSTOP, 0, 0, 0);
  };
  wcount = wcount + 1;
  if (wcount >= maxwords) {
    maxwords = textextend(maxwords, &words);
  };
}

static void checkbreak()
/* check that we are in not in chord, grace notes or tuple */
/* called at voice change */
{
  if (tuplecount != 0) {
    event_error("Previous voice has an unfinished tuple");
    tuplecount = 0;
  };
  if (v->inchord != 0) {
    event_error("Previous voice has incomplete chord");
    event_chordoff();
  };
  if (v->ingrace != 0) {
    event_error("Previous voice has unfinished grace notes");
    v->ingrace = 0;
  };
}

static void char_out(part, ch)
/* routine for building up part list */
struct vstring* part;
char ch;
{
/*
  if (*out - list >= MAXPARTS) {
    event_error("Expanded part is too large");
  } else {
    **out = ch;
    *out = *out + 1;
    parts = parts + 1;
  };
*/
  addch(ch, part);
  parts = parts + 1;
}

static void read_spec(spec, part)
/* converts a P: field to a list of part labels */
/* e.g. P:A(AB)3(CD)2 becomes P:AABABABCDCD */
/* A '+' indicates 'additive' behaviour (a part may include repeats).  */
/* A '-' indicates 'non-additive' behaviour (repeat marks in the music */
/* are ignored and only repeats implied by the part order statement    */
/* are played).  */
char spec[];
struct vstring* part;
{
  char* in;
  int i, j;
  int stackptr;
  char* stack[10];
  char lastch;

  stackptr = 0;
  in = spec;
  while (((*in >= 'A') && (*in <= 'Z')) || (*in == '(') || (*in == '.') ||
         (*in == ')') || (*in == '+') || (*in == '-') || 
         ((*in >= '0') && (*in <= '9'))) {
    if (*in == '.') {
      in = in + 1;
    };
    if (*in == '+') {
      additive = 1;
      in = in + 1;
    };
    if (*in == '-') {
      additive = 0;
      in = in + 1;
    };
    if ((*in >= 'A') && (*in <= 'Z')) {
      char_out(part, *in);
      lastch = *in;
      in = in + 1;
    };
    if (*in == '(') {
      if (stackptr < 10) {
        stack[stackptr] = part->st + strlen(part->st);
        stackptr = stackptr + 1;
      } else {
        event_error("nesting too deep in part specification");
      };
      in = in + 1;
    };
    if (*in == ')') {
      in = in + 1;
      if (stackptr > 0) {
        int repeats;
        char* start;
        char* stop;

        if ((*in >= '0') && (*in <= '9')) {
          repeats = readnump(&in);
        } else {
          repeats = 1;
        };
        stackptr = stackptr - 1;
        start = stack[stackptr];
        stop = part->st + strlen(part->st);
        for (i=1; i<repeats; i++) {
          for (j=0; j<((int) (stop-start)); j++) {
            char_out(part, *(start+j));
          };
        };
      } else {
        event_error("Too many )'s in part specification");
      };
    };
    if ((*in >= '0') && (*in <= '9')) {
      int repeats;

      repeats = readnump(&in);
      if (part->len > 0) {
        for (i = 1; i<repeats; i++) {
          char_out(part, lastch);
        };
      } else {
        event_error("No part to repeat in part specification");
      };
    };
  };
  if (stackptr != 0) {
    event_error("Too many ('s in part specification");
  };
}

void event_part(s)
/* handles a P: field in the abc */
char* s;
{
  char* p;

  if (dotune) {
    p = s;
    skipspace(&p);
    if (pastheader) {
      if (((int)*p < 'A') || ((int)*p > 'Z')) {
        event_error("Part must be one of A-Z");
        return;
      };
      if ((headerpartlabel == 1) && (part.st[0] == *p)) {
        /* P: field in header is not a label */
        headerpartlabel = 0;
        /* remove speculative part label */
        feature[part_start[(int)*p - (int)'A']] = NONOTE;
      } else {
        if (part_start[(int)*p - (int)'A'] != -1) {
          event_error("Part defined more than once");
        };
      };
      part_start[(int)*p - (int)'A'] = notes;
      addfeature(PART, (int)*p, 0, 0);
      checkbreak();
      v = getvoicecontext(1);
    } else {
      parts = 0;
      read_spec(p, &part);
      if (parts == 1) {
        /* might be a label not a specificaton */
        headerpartlabel = 1;
      };
    };
  };
}

void event_voice(n, s)
/* handles a V: field in the abc */
int n;
char *s;
{
  if (pastheader) {
    voicesused = 1;
    checkbreak();
    v = getvoicecontext(n);
    addfeature(VOICE, v->indexno, 0, 0);
  } else {
    event_warning("V: in header ignored");
  };
}

void event_length(n)
/* handles an L: field in the abc */
int n;
{
  if (pastheader) {
    v->default_length = n;
  } else {
    global.default_length = n;
  };
}

static void tempounits(t_num, t_denom)
/* interprets Q: once default length is known */
int *t_num, *t_denom;
{
  /* calculate unit for tempo */
  if (tempo_num == 0) {
    *t_num = 1;
    *t_denom = global.default_length;
  } else {
    if (relative_tempo) {
      *t_num = tempo_num;
      *t_denom = tempo_denom*global.default_length;
    } else {
      *t_num = tempo_num;
      *t_denom = tempo_denom;
    };
  };
}

void event_tempo(n, a, b, rel)
/* handles a Q: field e.g. Q: a/b = n  or  Q: Ca/b = n */
int n;
int a, b, rel;
{
  int t_num, t_denom;
  int new_div;
  long new_tempo;
  int tempo_l, tempo_h;

  if ((n == 0) || ((a!=0) && (b == 0))) {
    event_error("malformed Q: field ignored");
  } else {
    if (dotune) {
      if (pastheader) {
        tempo_num = a;
        tempo_denom = b;
        relative_tempo = rel;
        tempounits(&t_num, &t_denom);
        new_tempo = (long) 60*1000000*t_denom/(n*4*t_num);
        /* split up into short ints */
        tempo_l = new_tempo & 0xffff;
        tempo_h = new_tempo >> 16;
        new_div = (int) ((float)DIV*(float)new_tempo/(float)tempo + 0.5);
        addfeature(TEMPO, new_div, tempo_h, tempo_l);
      } else {
        Qtempo = n;
        tempo_num = a;
        tempo_denom = b;
        relative_tempo = rel;
      };
    };
  };
}

void event_timesig(n, m, dochecking)
/* handles an M: field  M:n/m */
int n, m, dochecking;
{
  if (dotune) {
    if (pastheader) {
      addfeature(TIME, dochecking, n, m);
    } else {
      time_num = n;
      time_denom = m;
      timesigset = 1;
      barchecking = dochecking;
    };
  };
}

void event_octave(num)
/* used internally by other routines when octave=N is encountered */
/* in I: or K: fields */
int num;
{
  if (dotune) {
    if (pastheader) {
      v->octaveshift = num;
    } else {
      global.octaveshift = num;
    };
  };
}

void event_info_key(key, value)
char* key;
char* value;
{
  int num;

  if (strcmp(key, "octave")==0) {
    num = readsnumf(value);
    event_octave(num);
  };
}

static void stack_broken(v)
struct voicecontext* v;
{
  v->broken_stack[0] = v->laststart;
  v->broken_stack[1] = v->lastend;
  v->broken_stack[2] = v->thisstart;
  v->broken_stack[3] = v->thisend;
  v->broken_stack[4] = v->brokentype;
  v->broken_stack[5] = v->brokenmult;
  v->broken_stack[6] = v->brokenpending;
  v->laststart = -1;
  v->lastend = -1;
  v->thisstart = -1;
  v->thisend = -1;
  v->brokenpending = -1;
}

static void restore_broken(v)
struct voicecontext* v;
{
  if (v->brokenpending != -1) {
    event_error("Unresolved broken rhythm in grace notes");
  };
  v->laststart = v->broken_stack[0];
  v->lastend = v->broken_stack[1];
  v->thisstart = v->broken_stack[2];
  v->thisend = v->broken_stack[3];
  v->brokentype = v->broken_stack[4];
  v->brokenmult = v->broken_stack[5];
  v->brokenpending = v->broken_stack[6];
}

void event_graceon()
/* a { in the abc */
{
  if (gracenotes) {
    event_error("Nested grace notes not allowed");
  } else {
    if (v->inchord) {
      event_error("Grace notes not allowed in chord");
    } else {
      gracenotes = 1;
      addfeature(GRACEON, 0, 0, 0);
      v->ingrace = 1;
      stack_broken(v);
    };
  };
}

void event_graceoff()
/* a } in the abc */
{
  if (!gracenotes) {
    event_error("} without matching {");
  } else {
    gracenotes = 0;
    addfeature(GRACEOFF, 0, 0, 0);
    v->ingrace = 0;
    restore_broken(v);
  };
}

void event_rep1()
/* [1 in the abc */
{
  addfeature(PLAY_ON_REP, 0, 0, 1);
/*
  if ((notes == 0) || (feature[notes-1] != SINGLE_BAR)) {
    event_error("[1 must follow a single bar");
  } else {
    feature[notes-1] = BAR1;
  };
*/
}

void event_rep2()
/* [2 in the abc */
{
  addfeature(PLAY_ON_REP, 0, 0, 2);
/*
  if ((notes == 0) || (feature[notes-1] != REP_BAR)) {
    event_error("[2 must follow a :| ");
  } else {
    feature[notes-1] = REP_BAR2;
  };
*/
}

void event_playonrep(s)
char* s;
/* [X in the abc, where X is a list of numbers */
{
  int num, converted;
  char seps[2];

  converted = sscanf(s, "%d%1[,-]", &num, seps);
  if (converted == 0) {
    event_error("corrupted variant ending");
  } else {
    if ((converted == 1) && (num != 0)) {
      addfeature(PLAY_ON_REP, 0, 0, num);
    } else {
      textfeature(PLAY_ON_REP, s);
    };
  };
}

static void slurtotie()
/* converts a pair of identical slurred notes to tied notes */
{
  int last1, slurtie, last2, failed;
  int j;

  if ((!v->ingrace) && (!v->inchord)) {
    j = notes-1;
    failed = 0;
    last1 = -1;
    while ((j>=0) && (!failed) && (last1 == -1)) {
      if (feature[j] == NOTE) {
        last1 = j;
      };
      if ((j<=0) || (feature[j] == REST) || (feature[j] == CHORDOFF) ||
          (feature[j] == GRACEOFF) || (feature[j] == SLUR_ON)) {
        failed = 1;
      };
      j = j - 1;
    };
    slurtie = -1;
    while ((j>=0) && (!failed) && (slurtie == -1)) {
      if (feature[j] == SLUR_TIE) {
        slurtie = j;
      };
      if ((j<=0) || (feature[j] == REST) || (feature[j] == CHORDOFF) ||
          (feature[j] == GRACEOFF) || (feature[j] == SLUR_ON) ||
          (feature[j] == NOTE)) {
        failed = 1;
      };
      j = j - 1;
    };
    last2 = -1;
    while ((j>=0) && (!failed) && (last2 == -1)) {
      if (feature[j] == NOTE) {
        last2 = j;
      };
      if ((j<=0) || (feature[j] == REST) || (feature[j] == CHORDOFF) ||
          (feature[j] == GRACEOFF) || (feature[j] == SLUR_ON)) {
        failed = 1;
      };
      j = j - 1;
    };
    if ((!failed) && (pitch[last1] == pitch[last2])) {
      /* promote SLUR_TIE to tie */
      feature[slurtie] = TIE;
      event_warning("Slur in abc taken to mean a tie");
    } else {
      if (verbose) {
        event_warning("Slur ignored");
      };
    };
  };
}

void event_sluron(t)
/* called when ( is encountered in the abc */
int t;
{
  if (t == 1) {
    addfeature(SLUR_ON, 0, 0, 0);
    v->inslur = 1;
  };
}

void event_sluroff(t)
/* called when ) is encountered */
int t;
{
  if (t == 0) {
    slurtotie();
    addfeature(SLUR_OFF, 0, 0, 0);
    v->inslur = 0;
  };
}

void event_tie()
/* a tie - has been encountered in the abc */
{
  addfeature(TIE, 0, 0, 0);
}

void event_space()
/* space character in the abc is ignored by abc2midi */
{
  /* ignore */
  /* printf("Space event\n"); */
}

void event_lineend(ch, n)
/* called when \ or ! or * or ** is encountered at the end of a line */
char ch;
int n;
{
  /* ignore */
}

void event_broken(type, mult)
/* handles > >> >>> < << <<< in the abc */
int type, mult;
{
  if (v->inchord) {
    event_error("Broken rhythm not allowed in chord");
  } else {
    if (v->ingrace) {
      event_error("Broken rhythm not allowed in grace notes");
    } else {
      if ((hornpipe) && (feature[notes-1] == GT)) {
        /* remove any superfluous hornpiping */
        notes = notes - 1;
      };
      /* addfeature(type, mult, 0, 0); */
      v->brokentype = type;
      v->brokenmult = mult;
      v->brokenpending = 0;
    };
  };
}

void event_tuple(n, q, r)
/* handles triplets (3 and general tuplets (n:q:r in the abc */
int n, q, r;
{
  if (tuplecount > 0) {
    event_error("nested tuples");
  } else {
    if (r == 0) {
      specialtuple = 0;
      tuplecount = n;
    } else {
      specialtuple = 1;
      tuplecount = r;
    };
    if (q != 0) {
      tfact_num = q;
      tfact_denom = n;
    } else {
      if ((n < 2) || (n > 9)) {
        event_error("Only tuples (2 - (9 allowed");
        tfact_num = 1;
        tfact_denom = 1;
        tuplecount = 0;
      } else {
        /* deduce tfact_num using standard abc rules */
        if ((n == 2) || (n == 4) || (n == 8)) tfact_num = 3;
        if ((n == 3) || (n == 6)) tfact_num = 2;
        if ((n == 5) || (n == 7) || (n == 9)) {
          if ((time_num % 3) == 0) {
            tfact_num = 3;
          } else {
            tfact_num = 2;
          };
        };
        tfact_denom = n;
      };
    };
    tnote_num = 0;
    tnote_denom = 0;
  };
}

void event_chord()
/* a + has been encountered in the abc */
{
  if (v->inchord) {
    event_chordoff();
  } else {
    event_chordon();
  };
}

static void lenmul(n, a, b)
/* multiply note length by a/b */
int n, a, b;
{
  if ((feature[n] == NOTE) || (feature[n] == REST) || 
      (feature[n] == CHORDOFF)) {
    num[n] = num[n] * a;
    denom[n] = denom[n] * b;
    reduce(&num[n], &denom[n]);
  };
}

static void applybroken(place, type, n)
int place, type, n;
/* adjust lengths of broken notes */
{
  int num1, num2, denom12;
  int j;
  int forechord, forestart, foreend, backchord, backstart, backend;
  int failed, lastnote;

  j = place;
  failed = 0;
  forestart = -1;
  foreend = -1;
  forechord = 0;
  /* find following note or chord */
  while ((!failed) && (forestart == -1)) {
    if ((feature[j] == NOTE) || (feature[j] == REST)) {
      forestart = j;
      if (forechord) {
        lastnote = forestart;
      } else {
        foreend = forestart;
      };
    };
    if ((feature[j] == GRACEON) || (feature[j] == TIE)) {
      event_error("Unexpected item following broken rhythm");
    };
    if (feature[j] == CHORDON) {
      forechord = 1;
    };
    j = j + 1;
    if (j>=notes) {
      failed = 1;
    };
  };
  /* look for extend of chord if there is one */
  while ((!failed) && (foreend == -1)) {
    if ((feature[j] == NOTE) || (feature[j] == REST)) {
      lastnote = j;
    };
    if ((feature[j] == GRACEON) || (feature[j] == TIE)) {
      event_error("Unexpected item following broken rhythm");
    };
    if (feature[j] == CHORDOFF) {
      foreend = j;
    };
    j = j + 1;
    if (j>=notes) {
      failed = 1;
    };
  };
  /* look for note or chord before broken rhythm symbol */
  j = place;
  backend = -1;
  backstart = -1;
  backchord = 0;
  while ((!failed) && (backend == -1)) {
    if ((feature[j] == NOTE) || (feature[j] == REST)) {
      backend = j;
      if (backchord) {
        lastnote = backend;
      } else {
        backstart = backend;
      };
    };
    if ((feature[j] == GRACEOFF) || (feature[j] == TIE)) {
      event_error("Unexpected item preceding broken rhythm");
    };
    if (feature[j] == CHORDOFF) {
      backchord = 1;
      backend = j;
    };
    j = j - 1;
    if (j<0) {
      failed = 1;
    };
  };
  /* look for extent of chord if there is one */
  while ((!failed) && (backstart == -1)) {
    if ((feature[j] == NOTE) || (feature[j] == REST)) {
      lastnote = j;
    };
    if ((feature[j] == GRACEON) || (feature[j] == TIE)) {
      event_error("Unexpected item following broken rhythm");
    };
    if (feature[j] == CHORDON) {
      backstart = lastnote;
    };
    j = j - 1;
    if (j<0) {
      failed = 1;
    };
  };
  switch(n) {
    case 1:
      num1 = 4;
      num2 = 2;
      break;
    case 2:
      num1 = 7;
      num2 = 1;
      break;
    case 3:
      num1 = 15;
      num2 = 1;
      break;
  };
  denom12 = (num1 + num2)/2;
  if (type == LT) {
    j = num1;
    num1 = num2;
    num2 = j;
  };
  /* check for same length notes */
  if ((num[backstart]*denom[forestart]) != 
             (num[forestart]*denom[backstart])) {
    failed = 1;
  };
  if (failed) {
    event_error("Cannot apply broken rhythm");
  } else {
    for (j=backstart; j<=backend; j++) {
      lenmul(j, num1, denom12);
    };
    for (j=forestart; j<=foreend; j++) {
      lenmul(j, num2, denom12);
    };
  };
}

static void brokenadjust()
/* adjust lengths of broken notes */
{
  int num1, num2, denom12;
  int j;
  int failed;

  switch(v->brokenmult) {
    case 1:
      num1 = ratio_b;
      num2 = ratio_a;
      break;
    case 2:
      num1 = 7;
      num2 = 1;
      break;
    case 3:
      num1 = 15;
      num2 = 1;
      break;
  };
  denom12 = (num1 + num2)/2;
  if (v->brokentype == LT) {
    j = num1;
    num1 = num2;
    num2 = j;
  };
  failed = 0;
  if ((v->laststart == -1) || (v->lastend == -1) || 
      (v->thisstart == -1) || (v->thisend == -1)) {
    failed = 1;
  } else {
    /* check for same length notes */
    if ((num[v->laststart]*denom[v->thisstart]) != 
             (num[v->thisstart]*denom[v->laststart])) {
      failed = 1;
    };
  };
  if (failed) {
    event_error("Cannot apply broken rhythm");
  } else {
/*
    printf("Adjusting %d to %d and %d to %d\n",
           v->laststart, v->lastend, v->thisstart, v->thisend);
*/
    for (j=v->laststart; j<=v->lastend; j++) {
      lenmul(j, num1, denom12);
    };
    for (j=v->thisstart; j<=v->thisend; j++) {
      lenmul(j, num2, denom12);
    };
  };
}

static void marknotestart()
/* voice data structure keeps a record of last few notes encountered */
/* in order to process broken rhythm. This is called at the start of */
/* a note or chord */
{
  v->laststart = v->thisstart;
  v->lastend = v->thisend;
  v->thisstart = notes-1;
}

static void marknoteend()
/* voice data structure keeps a record of last few notes encountered */
/* in order to process broken rhythm. This is called at the end of */
/* a note or chord */
{
  v->thisend = notes-1;
  if (v->brokenpending != -1) {
    v->brokenpending = v->brokenpending + 1;
    if (v->brokenpending == 1) {
      brokenadjust();
      v->brokenpending = -1;
    };
  };
}

static void marknote()
/* when handling a single note, not a chord, marknotestart() and */
/* marknoteend() can be called together */
{
  marknotestart();
  marknoteend();
}

void event_rest(n,m)
/* rest of n/m in the abc */
int n, m;
{
  int num, denom;

  num = n;
  denom = m;
  if (v->inchord) v->chordcount = v->chordcount + 1;
  if (tuplecount > 0) {
    num = num * tfact_num;
    denom = denom * tfact_denom;
    if (tnote_num == 0) {
      tnote_num = num;
      tnote_denom = denom;
    } else {
      if (tnote_num * denom != num * tnote_denom) {
        if (!specialtuple) {
          event_warning("Different length notes in tuple");
        };
      };
    };
    if ((!gracenotes) &&
        ((!v->inchord) || ((v->inchord) && (v->chordcount == 1)))) {
      tuplecount = tuplecount - 1;
    };
  };
  if (v->chordcount == 1) {
    v->chord_num = num*4;
    v->chord_denom = denom*(v->default_length);
  };
  if ((!v->ingrace) && ((!v->inchord)||(v->chordcount==1))) {
    addunits(num, denom*(v->default_length));
  };
  last_num = 3; /* hornpiping (>) cannot follow rest */
  addfeature(REST, 0, num*4, denom*(v->default_length));
  if (!v->inchord ) {
    marknote();
  };
}

void event_chordon()
/* handles a chord start [ in the abc */
{
  if (v->inchord) {
    event_error("Attempt to nest chords");
  } else {
    addfeature(CHORDON, 0, 0, 0);
    v->inchord = 1;
    v->chordcount = 0;
    v->chord_num = 0;
    v->chord_denom = 1;
    marknotestart();
  };
}

void event_chordoff()
/* handles a chord close ] in the abc */
{
  if (!v->inchord) {
    event_error("Chord already finished");
  } else {
    addfeature(CHORDOFF, 0, v->chord_num, v->chord_denom);
    v->inchord = 0;
    v->chordcount = 0;
    marknoteend();
  };
}

void event_finger(p)
/* a 1, 2, 3, 4 or 5 has been found in a guitar chord field */
char *p;
{
  /* does nothing */
}

static int pitchof(note, accidental, mult, octave)
/* finds MIDI pitch value for note */
char note, accidental;
int mult, octave;
{
  int p;
  char acc;
  int mul, noteno;
  static int scale[7] = {0, 2, 4, 5, 7, 9, 11};
  char *anoctave = "cdefgab";

  p = (int) ((long) strchr(anoctave, note) - (long) anoctave);
  p = scale[p];
  acc = accidental;
  mul = mult;
  noteno = (int)note - 'a';
  if (acc == ' ') {
    acc = v->workmap[noteno];
    mul = v->workmul[noteno];
  } else {
    if (retain_accidentals) {
      v->workmap[noteno] = acc;
      v->workmul[noteno] = mul;
    };
  };
  if (acc == '^') p = p + mul;
  if (acc == '_') p = p - mul;
  return p + 12*octave + middle_c;
}

static void doroll(note, octave, n, m, pitch)
/* applies a roll to a note */
char note;
int octave, n, m;
int pitch;
{
  char up, down;
  int t;
  int upoct, downoct, pitchup, pitchdown;
  char *anoctave = "cdefgab";

  upoct = octave;
  downoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  down = *(anoctave + ((t+6) % 7));
  if (up == 'c') upoct = upoct + 1;
  if (down == 'b') downoct = downoct - 1;
  pitchup = pitchof(up, v->basemap[(int)up - 'a'], 1, upoct);
  pitchdown = pitchof(down, v->basemap[(int)down - 'a'], 1, downoct);
  addfeature(NOTE, pitch, n*4, m*(v->default_length)*5);
  marknotestart();
  addfeature(NOTE, pitchup, n*4, m*(v->default_length)*5);
  addfeature(NOTE, pitch, n*4, m*(v->default_length)*5);
  addfeature(NOTE, pitchdown, n*4, m*(v->default_length)*5);
  addfeature(NOTE, pitch, n*4, m*(v->default_length)*5);
  marknoteend();
}

static void dotrill(note, octave, n, m, pitch)
/* applies a trill to a note */
char note;
int octave, n, m;
int pitch;
{
  char up;
  int i, t;
  int upoct, pitchup;
  char *anoctave = "cdefgab";
  int a, b, count;

  upoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  if (up == 'c') upoct = upoct + 1;
  pitchup = pitchof(up, v->basemap[(int)up - 'a'], 1, upoct);
  a = 4;
  b = m*(v->default_length);
  count = n;
  while ((tempo*a)/((long)b) > 100000L) {
    count = count*2;
    b = b*2;
  };
  i = 0;
  while (i < count) {
    if (i == count - 1) {
      marknotestart();
    };
    if (i%2 == 0) {
      addfeature(NOTE, pitchup, a, b);
    } else {
      addfeature(NOTE, pitch, a, b);
    };
    i = i + 1;
  };
  marknoteend();
}

static void hornp(num, denom)
/* If we have used R:hornpipe, this routine modifies the rhythm by */
/* applying appropriate broken rhythm */
int num, denom;
{
  if ((hornpipe) && (notes > 0) && (feature[notes-1] != GT)) {
    if ((num*last_denom == last_num*denom) && (num == 1) &&
        (denom*time_num == 32)) {
      if (((time_num == 4) && (bar_denom == 8)) ||
          ((time_num == 2) && (bar_denom == 16))) {
           /* addfeature(GT, 1, 0, 0); */
           v->brokentype = GT;
           v->brokenmult = 1;
           v->brokenpending = 0;
      };
    };
    last_num = num;
    last_denom = denom;
  };
}

void event_note(decorators, accidental, mult, note, xoctave, n, m)
/* handles a note in the abc */
int decorators[DECSIZE];
int mult;
char accidental, note;
int xoctave, n, m;
{
  int pitch;
  int num, denom;
  int octave;

  octave = xoctave + v->octaveshift;
  num = n;
  denom = m;
  if (v->inchord) v->chordcount = v->chordcount + 1;
  if (tuplecount > 0) {
    num = num * tfact_num;
    denom = denom * tfact_denom;
    if (tnote_num == 0) {
      tnote_num = num;
      tnote_denom = denom;
    } else {
      if (tnote_num * denom != num * tnote_denom) {
        if (!specialtuple) {
          event_warning("Different length notes in tuple");
        };
      };
    };
    if ((!gracenotes) &&
        ((!v->inchord) || ((v->inchord) && (v->chordcount == 1)))) {
      tuplecount = tuplecount - 1;
    };
  };
  if ((!v->ingrace) && (!v->inchord)) {
    hornp(num, denom*(v->default_length));
  } else {
    last_num = 3; /* hornpiping (>) cannot follow chord or grace notes */
  };
  if ((!v->ingrace) && ((!v->inchord)||(v->chordcount==1))) {
    addunits(num, denom*(v->default_length));
  };
  pitch = pitchof(note, accidental, mult, octave);
  if (decorators[FERMATA]) {
    num = num*2;
  };
  if (v->chordcount == 1) {
    v->chord_num = num*4;
    v->chord_denom = denom*(v->default_length);
  };
  if ((decorators[ROLL]) || (decorators[ORNAMENT]) || (decorators[TRILL])) {
    if (v->inchord) {
      event_error("Rolls and trills not supported in chords");
    } else {
      if (decorators[TRILL]) {
        dotrill(note, octave, num, denom, pitch);
      } else { 
        doroll(note, octave, num, denom, pitch);
      };
    };
  } else {
    if (decorators[STACCATO]) {
      if (v->inchord) {
        if (v->chordcount == 1) {
          addfeature(REST, pitch, num*4, denom*(v->default_length));
        };
        addfeature(NOTE, pitch, num*4, denom*2*(v->default_length));
      } else {
        addfeature(NOTE, pitch, num*4, denom*2*(v->default_length));
        marknotestart();
        addfeature(REST, pitch, num*4, denom*2*(v->default_length));
        marknoteend();
      };
    } else {
      addfeature(NOTE, pitch, num*4, denom*(v->default_length));
      marknote();
      if ((v->inslur) && (!v->ingrace)) {
        addfeature(SLUR_TIE, 0, 0, 0);
      };
    };
  };
}

char *get_accidental(place, accidental)
/* read in accidental - used by event_handle_gchord() */
char *place; /* place in string being parsed */
char *accidental; /* pointer to char variable */
{
  char *p;

  p = place;
  *accidental = '=';
  if (*p == '#') {
    *accidental = '^';
    p = p + 1;
  };
  if (*p == 'b') {
    *accidental = '_';
    p = p + 1;
  };
  return(p);
}

void event_handle_gchord(s)
/* handler for the guitar chords */
char* s;
{
  int basepitch;
  char accidental, note;
  char* p;
  char name[9];
  int i;
  int chordno;
  int bassnote;
  int inversion;

  if ((*s >= '0') && (*s <= '5')) {
    event_finger(s);
    return;
  };
  p = s;
  if ((*p >= 'A') && (*p <= 'G')) {
    note = *p - (int) 'A' + (int) 'a';
    bassnote = 0;
    p = p + 1;
  } else {
    if ((*p >= 'a') && (*p <= 'g')) {
      note = *p;
      bassnote = 1;
      p = p + 1;
    } else {
      /* Experimental feature supports "_ignored words" */
      if (strchr("_^<>@", (int)*p) == NULL) {
        event_error("Guitar chord does not start with A-G or a-g");
      };
      return;
    };
  };
  p = get_accidental(p, &accidental);
  basepitch = pitchof(note, accidental, 1, 0) - middle_c;
  i = 0;
  while ((i<9) && (*p != ' ') && (*p != '\0') && (*p != '(') && (*p != '/')) {
    name[i] = *p;
    i = i+1;
    p = p + 1;
  };
  inversion = -1;
  if (*p == '/') {
    p = p + 1;
    if ((*p < 'A') || (*p > 'G')) {
      event_error(" / must be followed by A-G in chord");
    } else {
      note = (int)*p - 'A' + 'a';
      p = p + 1;
      p = get_accidental(p, &accidental);
      inversion = pitchof(note, accidental, 1, 0) - middle_c;
    };
  };
  name[i] = '\0';
  chordno = getchordnumber(name);
  if (chordno == 0) {
    char msg[40];

    sprintf(msg, "Unrecognized chord name \"%s\"", name);
    event_error(msg);
    chordno = 1; /* defaults to major */
  } else {
    /* only record voice as having chords if we recognize chord type */
    v->hasgchords = 1;
    if ((gchordvoice != 0) && (gchordvoice != v->indexno)) {
      event_warning("More than one voice with guitar chords in");
    };
    gchordvoice = v->indexno;
  };
  if (bassnote) {
    chordno = -1;
  };
  addfeature(GCHORD, basepitch, inversion, chordno);
}

void event_handle_instruction(s)
/* handler for ! ! instructions */
/* does ppp pp p mp mf f ff fff */
/* also does !drum! and !nodrum! */
char* s;
{
  char buff[MAXLINE];
  char* p;
  char* q;
  int done;

  p = s;
  /* remove any leading spaces */
  skipspace(&p);
  /* remove any trailing spaces */
  q = p;
  while ((*q != '\0') && (*q != ' ')) {
    q = q + 1;
  };
  if (*q == ' ') {
    *q = '\0';
  };
  done = 0;
  if (strcmp(p, "ppp") == 0) {
    event_specific("MIDI", "beat 30 20 10 1");
    done = 1;
  };
  if (strcmp(p, "pp") == 0) {
    event_specific("MIDI", "beat 45 35 20 1");
    done = 1;
  };
  if (strcmp(p, "p") == 0) {
    event_specific("MIDI", "beat 60 50 35 1");
    done = 1;
  };
  if (strcmp(p, "mp") == 0) {
    event_specific("MIDI", "beat 75 65 50 1");
    done = 1;
  };
  if (strcmp(p, "mf") == 0) {
    event_specific("MIDI", "beat 90 80 65 1");
    done = 1;
  };
  if (strcmp(p, "f") == 0) {
    event_specific("MIDI", "beat 105 95 80 1");
    done = 1;
  };
  if (strcmp(p, "ff") == 0) {
    event_specific("MIDI", "beat 120 110 95 1");
    done = 1;
  };
  if (strcmp(p, "fff") == 0) {
    event_specific("MIDI", "beat 127 125 110 1");
    done = 1;
  };
  if (strcmp(p, "drum") == 0) {
    addfeature(DRUMON, 0, 0, 0);
    if ((drumvoice != 0) && (drumvoice != v->indexno)) {
      event_warning("Implementation limit: drums only supported in one voice");
    };
    drumvoice = v->indexno;
    done = 1;
  };
  if (strcmp(p, "nodrum") == 0) {
    addfeature(DRUMOFF, 0, 0, 0);
    done = 1;
  };
  if (done == 0) {
    sprintf(buff, "instruction !%s! ignored", s);
    event_warning(buff);
  };
}

static void setmap(sf, map, mult)
/* work out accidentals to be applied to each note */
int sf; /* number of sharps in key signature -7 to +7 */
char map[7];
int mult[7];
{
  int j;

  for (j=0; j<7; j++) {
    map[j] = '=';
    mult[j] = 1;
  };
  if (sf >= 1) map['f'-'a'] = '^';
  if (sf >= 2) map['c'-'a'] = '^';
  if (sf >= 3) map['g'-'a'] = '^';
  if (sf >= 4) map['d'-'a'] = '^';
  if (sf >= 5) map['a'-'a'] = '^';
  if (sf >= 6) map['e'-'a'] = '^';
  if (sf >= 7) map['b'-'a'] = '^';
  if (sf <= -1) map['b'-'a'] = '_';
  if (sf <= -2) map['e'-'a'] = '_';
  if (sf <= -3) map['a'-'a'] = '_';
  if (sf <= -4) map['d'-'a'] = '_';
  if (sf <= -5) map['g'-'a'] = '_';
  if (sf <= -6) map['c'-'a'] = '_';
  if (sf <= -7) map['f'-'a'] = '_';
}

static void altermap(v, modmap, modmul)
/* apply modifiers to a set of accidentals */
struct voicecontext* v;
char modmap[7];
int modmul[7];
{
  int i;

  for (i=0; i<7; i++) {
    if (modmap[i] != ' ') {
      v->basemap[i] = modmap[i];
      v->basemul[i] = modmul[i];
    };
  };
}

static void copymap(v)
/* sets up working map at the start of each bar */
struct voicecontext* v;
{
  int j;

  for (j=0; j<7; j++) {
    v->workmap[j] = v->basemap[j];
    v->workmul[j] = v->basemul[j];
  };
}

/* workaround for problems with PCC compiler */
/* data may be written to an internal buffer */

int myputc(c)
char c;
{
  return (putc(c,fp));
}

static void addfract(xnum, xdenom, a, b)
/* add a/b to the count of units in the bar */
int *xnum;
int *xdenom;
int a, b;
{
  *xnum = (*xnum)*b + a*(*xdenom);
  *xdenom = (*xdenom) * b;
  reduce(xnum, xdenom);
}

static void dotie(j, xinchord)
/* called in preprocessing stage to handle ties */
int j, xinchord;
{
  int tienote, place;
  int tietodo, done;
  int lastnote, lasttie;
  int inchord;
  int tied_num, tied_denom;

  /* find note to be tied */
  tienote = j;
  while ((tienote > 0) && (feature[tienote] != NOTE) &&
         (feature[tienote] != REST)) {
    tienote = tienote - 1;
  };
  if (feature[tienote] != NOTE) {
    event_error("Cannot find note before tie");
  } else {
    inchord = xinchord;
    /* change NOTE + TIE to TNOTE + REST */
    feature[tienote] = TNOTE;
    feature[j] = REST;
    num[j] = num[tienote];
    denom[j] = denom[tienote];
    place = j;
    tietodo = 1;
    lasttie = j;
    tied_num = num[tienote];
    tied_denom = denom[tienote];
    done = 0;
    while ((place < notes) && (tied_num >=0) && (done == 0)) {
      switch (feature[place]) {
        case NOTE:
          lastnote = place;
          if ((tied_num == 0) && (tietodo == 0)) {
            done = 1;
          };
          if ((pitch[place] == pitch[tienote]) && (tietodo == 1)) {
            /* tie in note */
            if (tied_num != 0) {
              event_error("Time mismatch at tie");
            };
            tietodo = 0;
            /* add time to tied time */
            addfract(&tied_num, &tied_denom, num[place], denom[place]);
            /* add time to tied note */
            addfract(&num[tienote], &denom[tienote], num[place], denom[place]);
            /* change note to a rest */
            feature[place] = REST;
            /* get rid of tie */
            if (lasttie != j) {
              feature[lasttie] = OLDTIE;
            };
          };
          if (inchord == 0) {
            /* subtract time from tied time */
            addfract(&tied_num, &tied_denom, -num[place], denom[place]);
          };
          break;
        case REST:
          if ((tied_num == 0) && (tietodo == 0)) {
            done = 1;
          };
          if (inchord == 0) {
            /* subtract time from tied time */
            addfract(&tied_num, &tied_denom, -num[place], denom[place]);
          };
          break;
        case TIE:
          if (pitch[lastnote] == pitch[tienote]) {
            lasttie = place;
            tietodo = 1;
          };
          break;
        case CHORDON:
          inchord = 1;
          break;
        case CHORDOFF:
          inchord = 0;
          /* subtract time from tied time */
          addfract(&tied_num, &tied_denom, -num[place], denom[place]);
          break;
        default:
          break;
      };
      place = place + 1;
    };
    if (tietodo == 1) {
      event_error("Could not find note to be tied");
    };
  };
}

static void tiefix()
/* connect up tied notes */
{
  int j;
  int inchord;
  int chord_num, chord_denom;

  j = 0;
  inchord = 0;
  while (j<notes) {
    switch (feature[j]) {
    case CHORDON:
      inchord = 1;
      chord_num = -1;
      j = j + 1;
      break;
    case CHORDOFF:
      if (!((!inchord) || (chord_num == -1))) {
        num[j] = chord_num;
        denom[j] = chord_denom;
      };
      inchord = 0;
      j = j + 1;
      break;
    case NOTE:
      if ((inchord) && (chord_num == -1)) {
        chord_num = num[j];
        chord_denom = denom[j];
      };
      j = j + 1;
      break;
    case REST:
      if ((inchord) && (chord_num == -1)) {
        chord_num = num[j];
        chord_denom = denom[j];
      };
      j = j + 1;
      break;
    case TIE:
      dotie(j, inchord);
      j = j + 1;
      break;
    case LINENUM:
      lineno = pitch[j];
      j = j + 1;
      break;
    default:
      j = j + 1;
      break;
    };
  };
}

static void applygrace(place)
int place;
/* assign lengths to grace notes before generating MIDI */
{
  int start, end, p;
  int next_num, next_denom;
  int fact_num, fact_denom;
  int grace_num, grace_denom;
  int j;
  int nextinchord;
  int hostnotestart, hostnoteend;

  j = place;
  start = -1;
  while ((j < notes) && (start == -1)) {
    if (feature[j] == GRACEON) {
      start = j;
    };
    if (feature[j] == GRACEOFF) {
      event_error("} with no matching {");
    };
    j = j + 1;
  };
  /* now find end of grace notes */
  end = -1;
  while ((j < notes) && (end == -1)) {
    if (feature[j] == GRACEOFF) {
      end = j;
    };
    if ((feature[j] == GRACEON) && (j != start - 1)) {
      event_error("nested { not allowed");
    };
    j = j + 1;
  };
  /* now find following note */
  nextinchord = 0;
  hostnotestart = -1;
  while ((hostnotestart == -1) && (j < notes)) {
    if ((feature[j] == NOTE) || (feature[j] == REST)) {
      hostnotestart = j;
    };
    if (feature[j] == GRACEON) {
      event_error("Intervening note needed between grace notes");
    };
    if (feature[j] == CHORDON) {
      nextinchord = 1;
    };
    j = j + 1;
  };
  hostnoteend = -1;
  if (nextinchord) {
    while ((hostnoteend == -1) && (j < notes)) {
      if (feature[j] == CHORDOFF) {
        hostnotestart = j;
      };
      j = j + 1;
    };
  } else {
    hostnoteend = hostnotestart;
  };
  if (hostnotestart == -1) {
    event_error("No note found to follow grace notes");
  } else {
    /* count up grace units */
    grace_num = 0;
    grace_denom = 1;
    p = start;
    while (p <= end) {
      if ((feature[p] == NOTE) || (feature[p] == REST)) {
        grace_num = grace_num * denom[p] + grace_denom * num[p];
        grace_denom = grace_denom * denom[p];
        reduce(&grace_num, &grace_denom);
      };
      p = p + 1;
    };
    /* adjust host note or notes */
    p = hostnotestart;
    while (p <= hostnoteend) {
      if ((feature[p] == NOTE) || (feature[p] == REST) || 
          (feature[p] == CHORDOFF)) {
        next_num = num[p];
        next_denom = denom[p];
        num[p] = num[p] * (gfact_denom - gfact_num);
        denom[p] = next_denom * gfact_denom;
        reduce(&num[p], &denom[p]);
      };
      p = p + 1;
    };
    fact_num = next_num * grace_denom * gfact_num;
    fact_denom = next_denom * grace_num * gfact_denom;
    reduce(&fact_num, &fact_denom);
    /* adjust length of grace notes */
    p = start;
    while (p <= end) {
      lenmul(p, fact_num, fact_denom);
      p = p + 1;
    };
  };
}

static void dograce()
/* assign lengths to grace notes before generating MIDI */
{
  int j;

  j = 0;
  while (j < notes) {
    if (feature[j] == GRACEON) {
      applygrace(j);
    };
    if (feature[j] == SETGRACE) {
      gfact_num = pitch[j];
      gfact_denom = denom[j];
    };
    if (feature[j] == LINENUM) {
      lineno = pitch[j];
    };
    j = j + 1;
  };
}

static void zerobar()
/* start a new count of beats in the bar */
{
  bar_num = 0;
  bar_denom = 1;
}

void event_bar(type, replist)
/* handles bar lines of various types in the abc */
int type;
char* replist;
{
  int newtype;

  newtype = type;
  if ((type == THIN_THICK) || (type == THICK_THIN)) {
    newtype = DOUBLE_BAR;
  };
  if (type == BAR1) {
    newtype = SINGLE_BAR;
  };
  if (type == REP_BAR2) {
    newtype = REP_BAR;
  };
  addfeature(newtype, 0, 0, 0);
  copymap(v);
  zerobar();
  if (strlen(replist) > 0) {
    event_playonrep(replist);
  };
/*
  if (type == BAR1) {
    addfeature(PLAY_ON_REP, 0, 0, 1);
  };
  if (type == REP_BAR2) {
    addfeature(PLAY_ON_REP, 0, 0, 2);
  };
*/
}

static void delendrep(j)
int j;
/* remove bogus repeat */
{
  event_error("spurious repeat after second ending");
  switch(feature[j]) {
  case REP_BAR:
    feature[j] = DOUBLE_BAR;
    break;
  case DOUBLE_REP:
    feature[j] = BAR_REP;
    break;
  default:
    break;
  };
}

static void placeendrep(j)
/* patch up missing repeat */
int j;
{
  event_warning("Assuming repeat");
  switch(feature[j]) {
  case DOUBLE_BAR:
    feature[j] = REP_BAR;
    break;
  case SINGLE_BAR:
    feature[j] = REP_BAR;
    break;
  case BAR_REP:
    feature[j] = DOUBLE_REP;
    break;
  default:
    event_error("Internal error - please report");
    break;
  };
}

static void placestartrep(j)
/* patch up missing repeat */
int j;
{
  event_warning("Assuming repeat");
  switch(feature[j]) {
  case DOUBLE_BAR:
    feature[j] = BAR_REP;
    break;
  case SINGLE_BAR:
    feature[j] = BAR_REP;
    break;
  case REP_BAR:
    feature[j] = DOUBLE_REP;
    break;
  case BAR_REP:
    event_error("Too many end repeats");
    break;
  case DOUBLE_REP:
    event_error("Too many end repeats");
    break;
  default:
    event_error("Internal error - please report");
    break;
  };
}

static void fixreps()
/* find and correct missing repeats in music */
/* abc2midi places an extra || at the start of the music */
/* This can be converted to |: if necessary. */
{
  int j;
  int rep_point; /* where to assume a repeat starts */
  int expect_repeat;
  int expect_norepeat;
  int use_next;

  expect_repeat = 0;
  expect_norepeat = 0;
  use_next = 0;
  j = 0;
  while (j < notes) {
    switch(feature[j]) {
    case SINGLE_BAR:
      if (use_next) {
        rep_point = j;
        use_next = 0;
      };
      break;
    case DOUBLE_BAR:
      rep_point = j;
      use_next = 0;
      break;
    case BAR_REP:
      if (expect_repeat) {
        event_error("|: found when end repeat expected");
        placeendrep(j);
      };
      expect_repeat = 1;
      expect_norepeat = 0;
      use_next = 0;
      break;
    case REP_BAR:
      if (expect_norepeat) {
        delendrep(j);
      } else {
        if (!expect_repeat) {
          placestartrep(rep_point);
        };
      };
      expect_repeat = 0;
      expect_norepeat = 0;
      rep_point = j;
      use_next = 0;
      break;
    case PLAY_ON_REP:
      if (denom[j] == 1) {
        /* case REP1: */
        if (!expect_repeat) {
          placestartrep(rep_point);
        };
        expect_repeat = 1;
        expect_norepeat = 0;
        break;
      };
      if (denom[j] == 2) {
        /* case REP2: */
        if (expect_repeat) {
          event_error("require :|2 for second ending");
        };
        expect_repeat = 0;
        expect_norepeat = 1;
        break;
      };
    case REP_BAR2:
      /* cannot happen! */
      if (!expect_repeat) {
        placestartrep(rep_point);
      };
      expect_repeat = 0;
      use_next = 1;
      break;
    case DOUBLE_REP:
      if (expect_norepeat) {
        delendrep(j);
      } else {
        if (!expect_repeat) {
          placestartrep(rep_point);
        };
      };
      expect_repeat = 1;
      expect_norepeat = 0;
      break;
    default:
      break;
    };
    j = j + 1;
  };
}

static void startfile()
/* called at the beginning of an abc tune by event_refno */
/* This sets up all the default values */
{
  int j;

  if (verbose) {
    printf("scanning tune\n");
  };
  /* set up defaults */
  sf = 0;
  mi = 0;
  setmap(0, global.basemap, global.basemul);
  copymap(&global);
  global.octaveshift = 0;
  voicecount = 0;
  head = NULL;
  v = NULL;
  time_num = 4;
  time_denom = 4;
  timesigset = 0;
  barchecking = 1;
  global.default_length = -1;
  event_tempo(120, 1, 4, 0);
  notes = 0;
  ntexts = 0;
  gfact_num = 1;
  gfact_denom = 3;
  global_transpose = 0;
  hornpipe = 0;
  karaoke = 0;
  retain_accidentals = 1;
  ratio_a = 2;
  ratio_b = 4;
  wcount = 0;
  parts = -1;
  middle_c = 60;
  for (j=0; j<26; j++) {
    part_start[j] = -1;
  };
  headerpartlabel = 0;
  additive = 1;
  initvstring(&part);
  for (j=0; j<16;j++) {
    channels[j] = 0;
  };
  set_gchords("z");
  gchordvoice = 0;
  set_drums("z");
  drumvoice = 0;
  wordvoice = 0;
}

static void setbeat()
/* default accompaniment patterns for various time signatures */
{
  /* set up chord/fundamental sequence if not already set */
  if ((time_num == 2) && (time_denom == 2)) {
    set_gchords("fzczfzcz");
  };
  if (((time_num == 2) || (time_num == 4)) && (time_denom == 4)) {
    set_gchords("fzczfzcz");
  };
  if ((time_num == 3) && (time_denom == 4)) {
    set_gchords("fzczcz");
  };
  if ((time_num == 6) && (time_denom == 8)) {
    set_gchords("fzcfzc");
  };
  if ((time_num == 9) && (time_denom == 8)) {
    set_gchords("fzcfzcfzc");
  };
}

static void headerprocess()
/* called after the K: field has been reached, signifying the end of */
/* the header and the start of the tune */
{
  int t_num, t_denom;

  if (headerpartlabel == 1) {
    part_start[(int)part.st[0] - (int)'A'] = notes;
    addfeature(PART, part.st[0], 0, 0);
  };
  addfeature(DOUBLE_BAR, 0, 0, 0);
  pastheader = 1;

  gracenotes = 0; /* not in a grace notes section */
  if (!timesigset) {
    event_warning("No M: in header, using default");
  };
  /* calculate time for a default length note */
  if (global.default_length == -1) {
    if (((float) time_num)/time_denom < 0.75) {
      global.default_length = 16;
    } else {
      global.default_length = 8;
    };
  };
  bar_num = 0;
  bar_denom = 1;
  set_meter(time_num, time_denom);
  if (hornpipe) {
    if ((time_denom != 4) || ((time_num != 2) && (time_num != 4))) {
      event_error("Hornpipe must be in 2/4 or 4/4 time");
      hornpipe = 0;
    };
  };

  tempounits(&t_num, &t_denom);
  /* make tempo in terms of 1/4 notes */
  tempo = (long) 60*1000000*t_denom/(Qtempo*4*t_num);
  div_factor = division;
  setbeat();
  voicesused = 0;
}

void event_key(sharps, s, minor, modmap, modmul, gotkey, gotclef, clefname,
          octave, transpose, gotoctave, gottranspose)
/* handles a K: field */
int sharps; /* sharps is number of sharps in key signature */
int minor; /* a boolean 0 or 1 */
char *s; /* original string following K: */
char modmap[7]; /* array of accidentals to be applied */
int  modmul[7]; /* array giving multiplicity of each accent (1 or 2) */
int gotkey, gotclef;
int octave, transpose, gotoctave, gottranspose;
char* clefname;
{
  if ((dotune) && gotkey) {
    if (pastheader) {
      setmap(sharps, v->basemap, v->basemul);
      altermap(v, modmap, modmul);
      copymap(v);
      addfeature(KEY, sharps, 0, minor);
      if (gottranspose) {
        addfeature(TRANSPOSE, transpose, 0, 0);
      };
    } else {
      if (gottranspose) {
        global_transpose = transpose;
      };
      setmap(sharps, global.basemap, global.basemul);
      altermap(&global, modmap, modmul);
      copymap(&global);
      sf = sharps;
      mi = minor;
      headerprocess();
      v = newvoice(1);
      head = v;
    };
    if (gotoctave) {
      event_octave(octave);
    };
  };
}

static void finishfile()
/* end of tune has been reached - write out MIDI file */
{
  extern int nullputc();

  clearvoicecontexts();
  if (!pastheader) {
    event_error("No K: field found at start of tune");
  } else {
    int i;

    if (parts > -1) {
      addfeature(PART, ' ', 0, 0);
    };
    if (headerpartlabel == 1) {
      event_error("P: field in header should go after K: field");
    };
    if (verbose) {
      printf("handling grace notes\n");
    };
    dograce();
    tiefix();
    if ((parts == -1) && (voicecount == 1)) {
      if (verbose) {
        printf("fixing repeats\n");
      };
      fixreps();
    };
    if ((voicesused == 0) && (!karaoke) && (gchordvoice == 0) && 
        (drumvoice == 0)) {
      ntracks = 1;
    } else {
      ntracks = voicecount + karaoke + 1;
      if (gchordvoice != 0) {
        gchordtrack = ntracks;
        ntracks = ntracks + 1;
      };
      if (drumvoice != 0) {
        drumtrack = ntracks;
        ntracks = ntracks + 1;
      };
    };
    if (check) {
      Mf_putc = nullputc;
      if (ntracks == 1) {
        writetrack(0);
      } else {
        for (i=0; i<ntracks; i++) {
          writetrack(i);
        };
      };
    } else {
      if ((fp = fopen(outname, "wb")) == NULL) {
        event_fatal_error("File open failed");
      };
      printf("writing MIDI file %s\n", outname);
      Mf_putc = myputc;
      Mf_writetrack = writetrack;
      if (ntracks == 1) {
        mfwrite(0, 1, division, fp);
      } else {
        mfwrite(1, ntracks, division, fp);
      };
      fclose(fp);
#ifdef __MACINTOSH__
      (void) setOutFileCreator(outname,'Midi','ttxt');
#endif /* __MACINTOSH__ */

    };
    for (i=0; i<ntexts; i++) {
      free(atext[i]);
    };
    for (i=0; i<wcount; i++) {
      free(words[i]);
    };
    freevstring(&part);
  };
}

void event_blankline()
/* blank line found in abc signifies the end of a tune */
{
  if (dotune) {
    finishfile();
    parseroff();
    dotune = 0;
  };
}

void event_refno(n)
/* handles an X: field (which indicates the start of a tune) */
int n;
{
  if (dotune) {
    finishfile();
    parseroff();
    dotune = 0;
  };
  if (verbose) {
    printf("Reference X: %d\n", n);
  };
  if ((n == xmatch) || (xmatch == 0) || (xmatch == -1)) {
    if (xmatch == -1) {
      xmatch = -2;
    };
    parseron();
    dotune = 1;
    pastheader = 0;
    if (userfilename == 0) {
      if (outname != NULL) {
        free(outname);
      };
      /* Big enough for a 64-bit int! */
      outname = (char*)checkmalloc(strlen(outbase) + 22 + strlen(".mid"));
      sprintf(outname, "%s%d.mid", outbase, n);
    };
    startfile();
  };
}

void event_eof()
/* end of abc file encountered */
{
  if (dotune) {
    dotune = 0;
    parseroff();
    finishfile();
  };
  if (verbose) {
    printf("End of File reached\n");
  };
  free(pitch);
  free(num);
  free(denom);
  free(feature);
  free(words);
  free(outname);
  free(outbase);
}

