/*
 * 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)
 */

#define VERSION "2.15 September 28 2008"
/* enables reading V: indication in header */
#define XTEN1 1
/*#define INFO_OCTAVE_DISABLED 1*/

/* for Microsoft Visual C++ 6.0 and higher */
#ifdef _MSC_VER
#define ANSILIBS
#endif

#ifdef WIN32
#define snprintf _snprintf
#endif

#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
/*int snprintf(char *str, size_t size, const char *format, ...);*/


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

/* global variables grouped roughly by function */

FILE *fp;

programname fileprogram = ABC2MIDI;
extern int oldchordconvention; /* for handling +..+ chords */

/* 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;
int velocitychange = 15;
int chordstart=0;
int nopropagate_accidentals = 0;
/* microtonal support and scale temperament */
int active_pitchbend;
int microtone=0;
int temperament = 0;
#define SEMISIZE 4096
int octave_size = 12*SEMISIZE;
int fifth_size = 7*SEMISIZE; /* default to 12-edo */
int started_parsing=0;
int noGuitarChords = 0;


struct voicecontext {
  /* maps of accidentals for each stave line */
  char basemap[7], workmap[7][10];
  int  basemul[7], workmul[7][10];
  int  keyset; /* flag to indicate whether key signature set */
  int default_length;
  int voiceno; /* voice number referenced by V: command. To avoid
                  conflicts with split voices, all split voices
                  begin from 32. */
  int indexno; /* voice index number in the feat array. It just
                  increments by one and depends on the order the
                  voices are created -- including split voices.
               */
  int topvoiceno,topindexno; /* links to original voice in the split */
  int hasgchords;
  int haswords;
  int inslur;
  int ingrace;
  int octaveshift;
  int lastbarloc;  /* position of last bar line parsed */
  int tosplitno,fromsplitno; /* links to split voices and source voice*/ 
  int last_resync_point;
  /* 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; /* for handling broken rhythms*/
  /* broken rythm handling */
  int brokentype, brokenmult, brokenpending;
  int broken_stack[7];
  struct voicecontext* next;
  int drumchannel;
};
struct voicecontext global;
struct voicecontext* v;
struct voicecontext* head;
struct voicecontext* vaddr[64]; /* address of all voices (by v->indexno) */
/* vaddr is only a convenience for debugging */


int dependent_voice[64]; /* flag to indicate type of voice */
int voicecount;
int numsplits=0;
int splitdepth = 0;

/* 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;
int *bentpitch; /* needed for handling microtones */
featuretype *feature;
int *pitchline; /* introduced for handling ties */
int notes;

int verbose = 0;
int titlenames = 0;
int got_titlename;
int namelimit;
int xmatch;
int sf, mi;
int gchordvoice, wordvoice, drumvoice, dronevoice;
int gchordtrack, drumtrack, dronetrack;
int ratio_standard = -1; /* flag corresponding to -RS parameter */
/* when ratio_standard != -1 the ratio for a>b is 3:1 instead of 2:1 */
int quiet = -1; /* if not -1 many common warnings and error messages */
                /* are suppressed.                                   */
int fermata_fixed = 0; /* flag on how to process fermata */

/* 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;
int mtime_num, mtime_denom;
long tempo;
int tempo_num, tempo_denom;
int relative_tempo, Qtempo;
extern int division;
extern int div_factor;
int default_tempo = 120; /* quarter notes per minutes */

/* output file generation */
int userfilename = 0;
char *outname = NULL;
char *outbase = NULL;
int check;
int nofnop; /* for suppressing dynamics (ff, pp etc) */
int nocom;  /* for suppressing comments in MIDI file */
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 additive;
int gfact_num, gfact_denom, gfact_method;  /* for handling grace notes */

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

extern int decorators_passback[DECSIZE]; /* a kludge for passing
information from the event_handle_instruction to parsenote
in parseabc.c */

/* time signature after header processed */
int header_time_num,header_time_denom;

int dummydecorator[DECSIZE]; /* used in event_chord */
extern char* featname[];

void addfract(int *xnum, int *xdenom, int a, int b);
static void zerobar();
static void addfeature(int f,int p,int n,int d);
static void replacefeature(int f, int p, int n, int d, int loc);
static void insertfeature(int f, int p, int n, int d, int loc);
static void textfeature(int type, char *s);
extern long writetrack();
void init_drum_map();
static void fix_enclosed_note_lengths(int from, int end);
static int patchup_chordtie(int chordstart,int chordend);
static void copymap(struct voicecontext* v);


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

  s = (struct voicecontext*) checkmalloc(sizeof(struct voicecontext));
  voicecount = voicecount + 1;
  s->voiceno = n;
  s->indexno = voicecount;
  s->topvoiceno = n;
  s->topindexno = 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->lastbarloc = -1;
  s->laststart = -1;
  s->lastend = -1;
  s->thisstart = -1;
  s->thisend = -1;
  s->brokenpending = -1;
  s->tosplitno = -1;
  s->fromsplitno = -1;  
  s->last_resync_point=0;
  s->next = NULL;
    for (i=0; i<7; i++) {
      s->basemap[i] = global.basemap[i];
      s->basemul[i] = global.basemul[i];
      for (j=0;j<10;j++) {
        s->workmap[i][j] = global.workmap[i][j];
        s->workmul[i][j] = global.workmul[i][j];
        };
    }
  s->keyset = global.keyset;
  s->octaveshift = global.octaveshift;
  s->drumchannel = 0;
  vaddr[voicecount] = s;
  return(s);
}

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

  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;
  };
/* check that key signature mapping is set if global
 * key signature set.                             */
  if (p->keyset == 0 && global.keyset)
    {
    p->keyset = 1;
    for (i=0; i<7; i++) {
      p->basemap[i] = global.basemap[i];
      p->basemul[i] = global.basemul[i];
      for (j=0;j<10;j++) {
        p->workmap[i][j] = global.workmap[i][j];
        p->workmul[i][j] = global.workmul[i][j];
        }
      };
    }
  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 filename-from-tune-titles option */
  namelimit = 252;
  titlenames = 0;
  if (getarg("-t", argc, argv) != -1) {
    titlenames = 1;
    namelimit = 8;
  };
  /* look for verbose option */
  if (getarg("-v", argc, argv) != -1) {
    verbose = 1;
  } else {
    verbose = 0;
  };
  if (getarg("-ver",argc, argv) != -1) {
     printf("%s\n",VERSION);
     exit(0);
  }
/* look for "no forte no piano" option */
  if (getarg("-NFNP", argc, argv) != -1) {
    nofnop = 1;
  } else {
    nofnop = 0;
  }

  if (getarg("-NCOM", argc, argv) != -1) {
    nocom = 1;
  } else {
    nocom = 0;
  }


  if (getarg("-OCC",argc,argv) != -1) oldchordconvention=1;
  if (getarg("-NGC",argc,argv) != -1) noGuitarChords=1;
  if (getarg("-GC",argc,argv) != -1) noGuitarChords=0;

  maxnotes = 500;
  /* allocate space for notes */
  pitch = checkmalloc(maxnotes*sizeof(int));
  num = checkmalloc(maxnotes*sizeof(int));
  denom = checkmalloc(maxnotes*sizeof(int));
  bentpitch = checkmalloc(maxnotes*sizeof(int));
  feature = (featuretype*) checkmalloc(maxnotes*sizeof(featuretype));
  pitchline = checkmalloc(maxnotes*sizeof(int));
  for (j=0;j<DECSIZE;j++)  dummydecorator[j] = 0;

  /* 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 %s\n",VERSION);
    printf("Usage : abc2midi <abc file> [reference number] [-c] [-v] ");
    printf("[-o filename]\n");
    printf("        [-t] [-n <value>] [-RS] [-NFNP] [-NCOM]\n");
    printf("        [reference number] selects a tune\n");
    printf("        -c  selects checking only\n");
    printf("        -v  selects verbose option\n");
    printf("        -ver prints version number and exits\n");
    printf("        -o <filename>  selects output filename\n");
    printf("        -t selects filenames derived from tune titles\n");
    printf("        -n <limit> set limit for length of filename stem\n");
    printf("        -RS use 3:1 instead of 2:1 for broken rhythms\n");
    printf("        -quiet suppress some common warnings\n");
    printf("        -Q default tempo (quarter notes/minute)\n");
    printf("        -NFNP don't process !p! or !f!-like fields\n");
    printf("        -NCOM suppress comments in output MIDI file\n");
    printf("        -OCC old chord convention (eg. +CE+)\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< (int) strlen(outbase); j++) {
      if (outbase[j] == '.') outbase[j] = '\0';
    };
  };
  /* look for filename stem limit */
  j = getarg("-n", argc, argv);
  if (j != -1) {
    if (argc >= j+1) {
      namelimit = 0;
      sscanf(argv[j], "%d", &namelimit);
      if ((namelimit < 3) || (namelimit > 252)) {
        event_fatal_error("filename stem limit must be in the range 3 - 252");
      };
    } else {
      event_error("No number given, ignoring -n option");
    };
  };
  /* look for default tempo */
  j = getarg("-Q", argc,argv);
  if (j != -1) {
    if (argc >= j+1) {
      sscanf(argv[j], "%d", &default_tempo);
      if (default_tempo < 3) {
        event_fatal_error("Q parameter is too small\nEnter -Q 240 not -Q 1/4=240");
       };
    } else {
      event_error("No number given, ignoring -Q option");
    };
  };
       
  /* 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;
      };
      if (titlenames == 1) {
        event_warning("-o option over-rides -t option");
        titlenames = 0;
      };
    } else {
      event_error("No filename given, ignoring -o option");
    };
  };
  ratio_standard = getarg("-RS", argc, argv);
  quiet  = getarg("-quiet", argc, argv);
  dotune = 0;
  parseroff();
  setup_chordnames();
}

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

  /* to prevent possible buffer overflow in sprintf, which is */
  /* a security risk,  we truncate s */
#ifdef NO_SNPRINTF
  sprintf(msg,  "Ignoring text: %s", s); /* SS 2005-01-09 */
#else
  snprintf(msg, sizeof(msg), "Ignoring text: %s", s); /* AL 2005-01-04 */
#endif
  event_warning(msg);
}

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

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

void event_abbreviation(symbol, string, container)
/* abbreviation encountered - this is handled within the parser */
char symbol;
char *string;
char container;
{
}

void event_acciaccatura()
{
/* does nothing here but outputs a / in abc2abc */
return;
}


/* support functions for split voices */

static int locate_voice(int start, int indexno)
/* This function finds the beginning of voice:indexno
   in the feature array. It starts looking from j = start
*/
{
int j;
j = start;
while (j < notes) {
   if (feature[j] == VOICE && pitch[j] == indexno) {
       return j;
       }
  j++;
  }
return j;
}

static void sync_voice (struct voicecontext *vv, int sync_to, int ignorecurrentbar)
{
/* The function scans the contents of the feature[] array between,
   the last resync point and sync_to (or end of the feature array)
   depending on the ignorecurrentbar flag. It counts the time
   of all notes and rests in the topvoice voice belonging to
   voice vv and adds rests each time it encounters a bar line,
   to synchronize this voice with its topvoice. It also copies
   all repeat signs and dynamic markings in the topvoice and
   inserts them in the current voice. 

   This function is called by event_split_voice, complete_all_split_voices
   and event_bar (if split voices are present).

   The last_resync_point keeps track of what has been scanned,
   so we only scan the topvoice once.

*/
int j;
char *p;
char command[40];
int maxnotes,begin;
char message[80];
int snum,sdenom;
int insidechord;
int voiceno,indexno;
voiceno = vv->topvoiceno;
indexno = vv->topindexno;
snum = 0;
sdenom = 1;
begin =0; /* to bypass double bar or single bar at beginning */
/* we have already synced to last_resync_point so we continue
   from here.
*/
j = vv->last_resync_point;
if (voiceno != 1 || j>2)
 j = locate_voice(j,indexno);
else j++; /* bypass bar */
if (ignorecurrentbar) maxnotes = sync_to;
else maxnotes = notes-1; /*ignore last voice change */
insidechord = 0;
/*printf("syncing voice %d to %d from %d to %d \n",vv->indexno,indexno,j,maxnotes);*/
while (j<=maxnotes) {
/*  dumpfeat(j,j); */
  switch (feature[j]) {
    case VOICE:
       if (pitch[j] != indexno) 
          j = locate_voice(j,indexno);
          break;
       break;
    case CHORDON:
       insidechord = 1;
       break;
    case CHORDOFF:
    case CHORDOFFEX:
       insidechord = 0;
       break;
    case SINGLE_BAR:
    case DOUBLE_BAR:
    case BAR_REP:
    case REP_BAR:
    case DOUBLE_REP:
       if (snum>0) {  /* bypass REST if no notes processed */
         addfeature(REST,0,snum,sdenom);
         /*printf("  added %d/%d to voice %d\n",snum,sdenom,vv->indexno);*/
         snum = 0;
         sdenom =1;
         }
       addfeature(feature[j], 0, 0, denom[j]); /* copy feature */
       break;
    case PLAY_ON_REP:
        if (feature[j-1] == SINGLE_BAR || feature[j-1] == REP_BAR
           || feature[j-1] == VOICE) 
              addfeature(feature[j],0,0,denom[j]);
        else {
            sprintf(message,"expecting SINGLE_BAR or REP_BAR preceding"
            " PLAY_ON_REP instead found %s at %d\n",featname[feature[j-1]],j-1);
            event_error(message);
            }
        break;
    case DYNAMIC:
       p = atext[pitch[j]];
       skipspace(&p);
       readstr(command, &p, 40);
       if (strcmp(command, "program") == 0) {
          textfeature(DYNAMIC, atext[pitch[j]]);
         }
       break;
    case CHANNEL:
       addfeature(feature[j], pitch[j], 0, 0); /* copy feature */
       break;

    case TIME:
       addfeature(feature[j], pitch[j], num[j], denom[j]); /* copy feature */
       break; /* [SS] 2008-07-17 */

    case SETTRIM:
       addfeature(feature[j], pitch[j], num[j], denom[j]); /* copy feature */
       break; /* [SS] 2008-08-12 */

    case NOTE:
    case TNOTE:
    case REST:
       if (insidechord < 2) addfract(&snum,&sdenom,num[j],denom[j]);
       if (insidechord) insidechord++;
       begin = 1;
       break;
    default:
       break;
   }
   j++;
  }
/* There are no more indexno notes between maxnotes and notes-1,
   so set sync point at the end.
*/
vv->last_resync_point = notes-1;
}


int search_backwards_for_last_bar_line (int from)
{
int found,j;
found = 0;
j = from;
while (!found && j>0)
  {
  if (feature[j] ==  SINGLE_BAR || 
      feature[j] ==  DOUBLE_BAR ||
      feature[j] ==  BAR_REP    || 
      feature[j] ==  REP_BAR    ||
      feature[j] ==  PLAY_ON_REP ||
      feature[j] ==  DOUBLE_REP)  {found = 1; break;}
      j--;
  }
return j;
}



/* When a split voice is encountered for the first time, we
   must create a new voice and insert the proper delay (rests)
   so that it remains in sync with the starting voice. If
   the split voice already exists, we still sync it to the
   source voice (there may have been intervening bars with
   no splits) by adding rests. 
*/

int sync_to;

void event_split_voice()
{
int splitno;
int voiceno,indexno;
int topvoiceno,topindexno;
int program;
int octaveshift;
voicesused = 1; /* multivoice file */
splitno = v->tosplitno;
program = 0;

/* a voice split in bar is just like a bar line */
zerobar();
v->lastbarloc = notes;  /* in case we need to change it */

voiceno = v->voiceno;
indexno = v->indexno;
topvoiceno = v->topvoiceno;
topindexno = v->topindexno;
octaveshift = v->octaveshift;
if (topvoiceno == voiceno) sync_to = search_backwards_for_last_bar_line(notes-1);
addfeature(SINGLE_BAR,0,0,0);

if (splitno == -1) {splitno = 32+numsplits++;
                   v->tosplitno = splitno;
                   }
v = getvoicecontext(splitno);
splitdepth++;
addfeature(VOICE, v->indexno, 0, 0);
if (v->fromsplitno == -1) {
  v->fromsplitno = voiceno;
  v->topvoiceno = topvoiceno;
  v->topindexno = topindexno;
  v->octaveshift = octaveshift;
 }
dependent_voice[v->indexno] = 1;
/* when syncing the split voice we want to be sure that
   we do not include the notes in the last bar in the source
   voice the notes in the split voice take their place.
*/
sync_voice (v,sync_to,1);
}




void recurse_back_to_original_voice ()
{
int previous_voice;
previous_voice = v->fromsplitno;
while (previous_voice >-1 && splitdepth > 0) {
  v = getvoicecontext(previous_voice);
  previous_voice = v->fromsplitno;
  splitdepth--;
  }
addfeature(VOICE, v->indexno, 0, 0);
copymap(v);
}


void recurse_back_and_change_bar (int type)
{
int previous_voice;
previous_voice = v->fromsplitno;
while (previous_voice >-1 && splitdepth > 0) {
  v = getvoicecontext(previous_voice);
  if (v->lastbarloc > -1) replacefeature(type, 0,0,0, v->lastbarloc);
  previous_voice = v->fromsplitno;
  splitdepth--;
  }
addfeature(VOICE, v->indexno, 0, 0);
}


static void
complete_all_split_voices ()
{
int splitno;
struct voicecontext *p;
int voiceno,indexno;

v = head;
while (v != NULL) {
    splitno = v->tosplitno;
    if (splitno > -1) {
      voiceno = v->voiceno;
      indexno = v->indexno;
      p = getvoicecontext(splitno);
      addfeature(VOICE, p->indexno, 0, 0);
      sync_voice (p,0,0);
      /* complete fraction of bar */
      if(bar_num >0) addfeature(REST, 0, 4*bar_num, bar_denom);
      }
    v = v->next;
  };
}

/* end of code for split voices */


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);
#if !IPHONE
  exit(1);
#endif
}

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,*ptr2,*ptr3;
  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));
  ptr2 = checkmalloc(newlimit*sizeof(int));
  ptr3 = checkmalloc(newlimit*sizeof(int));
  for(i=0;i<maxnotes;i++){
    ptr[i] = pitch[i];
    ptr2[i] = pitchline[i];
    ptr3[i] = bentpitch[i];
  };
  free(pitch);
  free(pitchline);
  free(bentpitch);
  pitch = ptr;
  pitchline = ptr2;
  bentpitch = ptr3;
  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);
  };
}

static void replacefeature(f, p, n, d, loc)
int f, p, n, d, loc;
{
  feature[loc] = f;
  pitch[loc] = p;
  num[loc] = n;
  denom[loc] = d;
}


static void insertfeature(f, p, n, d, loc)
/* insert feature in internal table */
int f,p,n,d,loc;
{ int i;
  notes = notes + 1;
  if (notes >= maxnotes) {
    maxnotes = autoextend(maxnotes);
  };
  for (i=notes;i>loc;i--) {
    feature[i] = feature[i-1];
    pitch[i] = pitch[i-1];
    pitchline[i] = pitchline[i-1];
    bentpitch[i] = bentpitch[i-1];
    num[i] = num[i-1];
    denom[i] = denom[i-1];
    };
  feature[loc] = f;
  pitch[loc]   = p;
  num[i]       = n;
  denom[i]     = d;
  pitchline[i] = 0;
  bentpitch[i] = 0;
}

static void removefeature(loc)
int loc;
{
  int i;
  for (i=loc;i<notes;i++)
    {
    feature[i] = feature[i+1];
    pitch[i]   = pitch[i+1];
    num[i]     = num[i+1];
    denom[i]   = denom[i+1];
    pitchline[i] = pitchline[i+1];
    bentpitch[i] = bentpitch[i+1];
    }
  notes--;
}

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(int type, char *s)
/* called while parsing abc - stores an item which requires an */
/* associared string */
{
  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 (nocom) return;
  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;
  int done;

  if (started_parsing == 0) {
        event_specific_in_header(package,s);
        return;
        }
 
  if (strcmp(package, "MIDI") == 0) {
    int ch;
    int trans, rtrans;

    p = s;
    done = 0;
    skipspace(&p);
    readstr(command, &p, 40);
    if (strcmp(command, "channel") == 0) {
      skipspace(&p);
      ch = readnump(&p) - 1;
      if (v != NULL) {
        if (ch == 9) v->drumchannel = 1;
        else v->drumchannel = 0;
        }
      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 (trans == 0) {
          addfeature(GTRANSPOSE, val, 0, 0);
        } else {
          addfeature(RTRANSPOSE, val, 0, 0);
        };
      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, "fermatafixed") == 0) {
      fermata_fixed = 1;
      done = 1;
    };
    if (strcmp(command, "fermataproportional") == 0) {
      fermata_fixed = 0;
      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[200];

      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, 1, a, b);
        } else {
          gfact_num = a;
          gfact_denom = b;
        };
      };
      done = 1;
    };

    if(strcmp(command,"gracedivider") == 0) {
      int b;
      char msg[200];
      skipspace(&p);
      b = -1;
      b = readnump(&p);
     if (b < 2)
       {
       sprintf(msg, "a number 2 or larger should follow MIDI gracedivider");
      event_error(msg);
      }
      if (pastheader)
         addfeature(SETGRACE, 0, 1, b);
      else {gfact_denom = b; gfact_method = 0;}
      done = 1;
    }


    if (strcmp(command, "trim") == 0) {
      int a, b;
      skipspace(&p);
      a = readnump(&p);
      if (*p != '/') {
        event_error("Need / in MIDI trim command (eg trim 1/4)");
      } else {
        p = p + 1;
        b = readnump(&p);
       if (v != NULL) {
        addfeature(SETTRIM, 1, 4*a, b*v->default_length);
        } else {
        if (global.default_length == -1) 
          event_error("Need to define L: before trim command: trim command ignored.");
        else 
          addfeature(SETTRIM,1,4*a, b*global.default_length);
       };
      };
      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] = readsnump(&p); 
          i = i + 1;
        };
        addchordname(name, i, notes);
      };
      done = 1;
    };


  if (strcmp(command, "temperamentlinear") == 0) {
      double octave_cents=0.0;
      double fifth_cents=0.0;
      temperament = 1;
      middle_c = 60*SEMISIZE;

      if (sscanf(p," %lf %lf ",&octave_cents,&fifth_cents) == 2) {
        octave_size = (int)(octave_cents*SEMISIZE/100+0.5);
        fifth_size = (int)(fifth_cents*SEMISIZE/100+0.5);
      }
      else {
        event_error("Bad format for lineartemperament command");
      }
      done = 1;
    }

  if (strcmp(command, "temperamentnormal") == 0) {
      temperament = 0;
      event_normal_tone();
      done = 1;
      middle_c = 60;
      }


    if (strcmp(command,"drumon") == 0) {
      addfeature(DRUMON, 0, 0, 0);
      if ((drumvoice != 0) && (drumvoice != v->indexno)) {
        event_warning("Implementation limit: drums only supported in one voice");
       };
      if (v == NULL) event_error("%%MIDI drumon must occur after the first K: header");
      else {
           drumvoice = v->indexno;
           done = 1;
           }
    }
    if (strcmp(command,"drumoff") == 0) {
       addfeature(DRUMOFF, 0, 0, 0);
       done = 1;
    }

    if (strcmp(command,"droneon") == 0) {
      addfeature(DRONEON, 0, 0, 0);
      if ((dronevoice != 0) && (dronevoice != v->indexno)) {
        event_warning("Implementation limit: drones only supported in one voice");
       };
      if (v == NULL) event_error("%%MIDI droneon must occur after the first K: header");
      else {
           dronevoice = v->indexno;
           done = 1;
           }
    }
    if (strcmp(command,"droneoff") == 0) {
       addfeature(DRONEOFF, 0, 0, 0);
       done = 1;
    }

    if (strcmp(command, "deltaloudness") == 0) {
      skipspace(&p);
      velocitychange = readnump(&p);
      done = 1;
      }


    if (done == 0) {
      /* add as a command to be interpreted later */
      textfeature(DYNAMIC, s);
    };
  } else
  {
/* Parse %%abc directive */
      done = 0;
      if (strcmp(package, "abc") == 0)
      {
         p = s;
         skipspace(&p);
/* skip '-' character after abc */          
         p++;
         readstr(command, &p, 40);
/* Parse %%abc-copyright */
         if (strcmp(command, "copyright") == 0)
         {            
            int lnth;
            int n;
            char *ptr;

            done = 1;            
            skipspace(&p);
/* Copy string to parse.
 * Convert \n, \t, \r, \x and \\ in string to C style character constant defaults.
 * (Handles carriage return, linefeed and tab characters and hex codes.)
 */
            lnth = 0;
            ptr = &msg[0];
            while ((*p != '\0') && (lnth < 199))
            { 
               if (*p == '\\')
               {
                  p++;
                  switch (*p)
                  {
                     case 'n':
                        *ptr = '\n';
                        break;
                     case 'r':  
                        *ptr = '\r';
                        break;
                     case 't':  
                        *ptr = '\t';
                        break;
                     case '\\':
                        *ptr = *p;
                        break; 
                     case 'x':
                     case 'X':
                        p++;
                        sscanf (p, "%x", &n);
                        *ptr = n;
                        while ((*p != '\0') && (isxdigit (*p)))                     
                           p++;
                        p--;   
                        break;
                     default:
                        *ptr = *p;
                        break;
                  }
               }
               else
                  *ptr = *p;
               ptr++;
               p++;
              lnth++;          /* Anselm Lingnau 2005-01-04 */
            }
            *ptr = '\0';
            textfeature(COPYRIGHT, msg);
           if (*p != '\0') {   /* Anselm Lingnau 2005-01-04 */
               event_warning("ABC copyright notice abridged");
           }
        }
        else
        {

           /* Changed by Anselm Lingnau, 2005-01-04, for security */
#ifdef NO_SNPRINTF
          if (sprintf(msg, "%%%s%s", package, s) > sizeof(msg)) {
              event_warning("event_specific: comment too long");
          }
#else
          if (snprintf(msg,sizeof(msg), "%%%s%s", package, s) > sizeof(msg)) {
              event_warning("event_specific: comment too long");
          }
#endif
        event_comment(msg);
        }
      }
     if (strcmp(package, "propagate") == 0) {
          p = s;
          p = p + 13;
         printf("propagate-accidentals encountered\n");
         if (strcmp(p,"not") == 0) {
             nopropagate_accidentals = 1;
          } else {nopropagate_accidentals = 0;}
         done = 1;
            }

    if (done == 0)  
    {

/* changed by Anselm Lingnau on 2004-01-04, for security */
#ifdef NO_SNPRINTF
   if (sprintf(msg, "%%%s%s", package, s) > sizeof(msg)) {
      event_warning("event_specific: message too long");
   }
#else
   if (snprintf(msg, sizeof(msg), "%%%s%s", package, s) > sizeof(msg)) {
      event_warning("event_specific: message too long");
   }
#endif

    event_comment(msg);
    }
  }
}



/* global variables that can altered by %%MIDI before tune */
int default_middle_c = 60;
int default_retain_accidentals = 1;
int default_fermata_fixed = 0;
int default_ratio_a = 2;
int default_ratio_b = 4;


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

  if (strcmp(package, "MIDI") == 0) {

    p = s;
    done = 0;
    skipspace(&p);
    readstr(command, &p, 40);



    if (strcmp(command, "C") == 0) {
      int val;

      skipspace(&p);
      val = readnump(&p);
      default_middle_c = val;
      done = 1;
    };
    if (strcmp(command, "nobarlines") == 0) {
      default_retain_accidentals = 0;
      done = 1;
    };
    if (strcmp(command, "barlines") == 0) {
      default_retain_accidentals = 1;
      done = 1;
    };
    if (strcmp(command, "fermatafixed") == 0) {
      default_fermata_fixed = 1;
      done = 1;
    };
    if (strcmp(command, "fermataproportional") == 0) {
      default_fermata_fixed = 0;
      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)) {
        default_ratio_a = a;
        default_ratio_b = b;
        if (default_ratio_a + default_ratio_b % 2 == 1) {
          default_ratio_a = 2 * a;
          default_ratio_b = 2 * b;
        };
      } else {
        event_error("Invalid ratio");
      };
      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] = readsnump(&p); 
          i = i + 1;
        };
        addchordname(name, i, notes);
      };
      done = 1;
    };


    if (strcmp(command, "deltaloudness") == 0) {
      skipspace(&p);
      velocitychange = readnump(&p);
      done = 1;
      }

    if (done == 0) {
       event_warning("cannot handle this MIDI directive in file header");
    }
  }
}


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 extract_filename(char *f)
/* work out filename stem from tune title */
/* name length cannot exceed namelimit characters */
{
  char buffer[256];
  int i;
  char *p;

  i = 0;
  p = f;
  skipspace(&p);
  /* avoid initial 'The' or 'the' */
  if ((strncmp(p, "The", 3) == 0) || (strncmp(p, "the", 3) == 0)) {
    p = p + 3;
    skipspace(&p);
  };
  while ((*p != '\0') && (i < namelimit)) {
    if (isalnum(*p)) {
      buffer[i] = *p;
      i = i + 1;
    };
    p = p + 1;
  };
  buffer[i] = '\0';
  if (i == 0) {
    strcpy(buffer, "notitle");
    buffer[namelimit] = '\0';
  };
  strcpy(&buffer[strlen(buffer)], ".mid");
  if (outname != NULL) {
    free(outname);
  };
  outname = addstring(buffer);
  got_titlename = 1;
}

void event_field(char k, char* f)
/* Handles R: T: and any other field not handled elsewhere */
/* Added code to handle C: field. */
{
  if (dotune) {
    switch (k) {
    case 'T':
      textfeature(TITLE, f);
      if (titlenames && (!got_titlename)) {
        extract_filename(f);
      };
      break;
    case 'C':
      textfeature(COMPOSER, 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[256];
        
        if (strlen(f) < 256) {
          sprintf(buff, "%c:%s", k, f);
          textfeature(TEXT, buff);
        };
      };
    };
  } else {
    if (k == 'T') {
      event_warning("T: outside tune body - possible missing X:");
    };
  };
}

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

  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(1,1);
  };
  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 */

/********** This feature is not supported ********[SS] 2004-10-08      */
/* 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 == '+') {     no longer supported
*      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, vp)
/* handles a V: field in the abc */
int n;
char *s;
struct voice_params *vp;
{
  if (pastheader || XTEN1) {
    voicesused = 1;
    if (pastheader)  checkbreak();
    v = getvoicecontext(n);
    addfeature(VOICE, v->indexno, 0, 0);
    dependent_voice[v->indexno] = 0;
    if (vp->gotoctave) {
      event_octave(vp->octave,1);
    };
    if (vp->gottranspose) {
      addfeature(TRANSPOSE, vp->transpose, 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, pre, post)
/* handles a Q: field e.g. Q: a/b = n  or  Q: Ca/b = n */
/* strings before and after are ignored */
int n;
int a, b, rel;
char *pre;
char *post;
{
  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, local)
/* used internally by other routines when octave=N is encountered */
/* in I: or K: fields */
int num;
{
  if (dotune) {
    if (pastheader || local) {
      v->octaveshift = num;
    } else {
      global.octaveshift = num;
    };
  };
}

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

#ifdef INFO_OCTAVE_DISABLED
  return;
#endif
  if (strcmp(key, "octave")==0) {
    num = readsnumf(value);
    event_octave(num,0);
  };
  if (strcmp(key, "MIDI") == 0)
     event_specific(key, value);
}

static void stack_broken(v)
/* store away broken rhythm context on encountering grace notes */
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)
/* remember any broken rhythm context after grace notes */
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_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 (v->inslur) event_warning("Slur within slur");
 else {
      addfeature(SLUR_ON, 0, 0, 0);
      v->inslur = 1;
      }
}

void event_sluroff(t)
/* called when ) is encountered */
int t;
{
if (v->inslur) {
    /*slurtotie(); [SS] 2005-08-13 */
    addfeature(SLUR_OFF, 0, 0, 0);
    v->inslur = 0;
    }
else event_warning("No slur to close");
}


void event_tie()
/* a tie - has been encountered in the abc */
{
if (feature[notes-1] == CHORDOFF ||
    feature[notes-1] == CHORDOFFEX) { /* did a TIE connect with a chord */
       patchup_chordtie(chordstart,notes-1);
      }
  else
  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(1,1);
  } else {
    event_chordon(dummydecorator);
  };
}

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();
}

/* just a stub to ignore 'y' */
void event_spacing(n, m)
int n,m;
{
}

void event_rest(decorators,n,m,type)
/* rest of n/m in the abc */
int n, m,type;
int decorators[DECSIZE];
{
  int num, denom;

  num = n;
  denom = m;
  if (decorators[FERMATA]) {
    if (fermata_fixed) addfract(&num,&denom,1,1);
    else num = num*2;
  };
  if (v == NULL) {
    event_fatal_error("Internal error : no voice allocated");
  };
  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)) {
      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_mrest(n,m)
/* multiple bar rest of n/m in the abc */
/* we check for m == 1 in the parser */
int n, m;
{
  int i;
  int decorators[DECSIZE];
  decorators[FERMATA]=0;
/* it is not legal to pass a fermata to a multirest */

  for (i=0; i<n; i++) {
    event_rest(decorators,time_num*(v->default_length), time_denom,0);
    if (i != n-1) {
      event_bar(SINGLE_BAR, "");
    };
  };
}

void event_chordon(int chorddecorators[])
/* handles a chord start [ in the abc */
/* the array chorddecorators is needed in toabc.c and yapstree.c */
/* but is not relevant here.                                     */
{
  if (v->inchord) {
    event_error("Attempt to nest chords");
  } else {
    chordstart = notes;
    addfeature(CHORDON, 0, 0, 0);
    v->inchord = 1;
    v->chordcount = 0;
    v->chord_num = 0;
    v->chord_denom = 1;
    marknotestart();
  };
}


void event_chordoff(int chord_n, int chord_m)
/* handles a chord close ] in the abc */
{
  int c_n,c_m;
  if (chord_m == 1 && chord_n == 1) {
     c_m = denom[chordstart];
     c_n = num[chordstart];
     }
  else {c_m = chord_m; c_n = chord_n;}

  if (!v->inchord) {
    event_error("Chord already finished");
  } else {
  if (tuplecount > 0) {
    c_n = c_n * tfact_num;
    c_m = c_m * tfact_denom;
    if (tnote_num == 0) {
        tnote_num   = c_n;
        tnote_denom = c_m;
    } else {
       if (tnote_num * c_m != c_n * tnote_denom) {
          if (!specialtuple) {
            event_warning("Different length notes in tuple for chord");
           };
        };
     }
     if ((!gracenotes) && (!v->inchord)) {
        tuplecount = tuplecount - 1;
        };
    };

    if(chord_m == 1 && chord_n == 1) /* chord length not set outside [] */
      addfeature(CHORDOFF, 0, v->chord_num, v->chord_denom); 
    else
      {

      addfeature(CHORDOFFEX, 0, c_n*4, c_m*v->default_length);
      fix_enclosed_note_lengths(chordstart, notes-1);
      }
      
    v->inchord = 0;
    v->chordcount = 0;
    marknoteend();
    if (tuplecount > 0) --tuplecount;
  };
}

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, propogate_accs)
/* This code is used for handling gchords */
/* finds MIDI pitch value for note */
/* if propogate_accs is 1, apply any accidental to all instances of  */
/* that note in the bar. If propogate_accs is 0, accidental does not */
/* apply to other notes */
char note, accidental;
int mult, octave;
int propogate_accs;
{
  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 == ' ' && !microtone ) { 
/* if microtone do not propogate accidentals to this
   note.
*/
    acc = v->workmap[noteno][octave+4];
    mul = v->workmul[noteno][octave+4];
  } else {
    if ((retain_accidentals) && (propogate_accs)) {
      v->workmap[noteno][octave+4] = acc;
      v->workmul[noteno][octave+4] = mul;
    };
  };
  if (acc == '^') p = p + mul;
  if (acc == '_') p = p - mul;
  return p + 12*octave + middle_c;
}

static int barepitch(note, accidental, mult, octave) 
/* Computes MIDI pitch ignoring any key signature.
 * Required for drum track
 */
char note, accidental;
int mult, octave;
{
int p,pitch;
int accidental_size = 1;
static const char *anoctave = "cdefgab";
static int scale[7] = {0, 2, 4, 5, 7, 9, 11};
p = (int) ((long) strchr(anoctave, note) - (long) anoctave);
p = scale[p];
if (accidental == '^') p = p + mult*accidental_size;
if (accidental == '_') p = p - mult*accidental_size;
pitch = p + 12*octave + middle_c;
return pitch;
}

static int pitchof_b(note, accidental, mult, octave, propogate_accs,pitchbend)
/* computes MIDI pitch for note. If global temperament is set,
   it will apply a linear temperament and return a
   pitchbend. If propogate_accs is 1, apply any accidental to all
   instances of  that note in the bar. If propogate_accs is 0, 
   accidental does not apply to other notes */
char note, accidental;
int mult, octave;
int propogate_accs;
int *pitchbend;
{
  int p;
  char acc;
  int mul, noteno;
  int pitch4096,pitch,bend;

  static int scale[7] = {0, 2, 4, 5, 7, 9, 11};
  const int accidental_size = 7*fifth_size - 4*octave_size;
  const int tscale[7] = {
    0,
    2*fifth_size-octave_size,
    4*fifth_size-2*octave_size,
    -1*fifth_size+octave_size,
    fifth_size,
    3*fifth_size-octave_size,
    5*fifth_size-2*octave_size
  };

  static const char *anoctave = "cdefgab";

  acc = accidental;
  mul = mult;
  noteno = (int)note - 'a';
  if (acc == ' ' && !microtone) {
/* if microtone do not propogate accidentals to this
   note.
*/
    acc = v->workmap[noteno][octave+4];
    mul = v->workmul[noteno][octave+4];
  } else {
    if ((retain_accidentals) && (propogate_accs)) {
      v->workmap[noteno][octave+4] = acc;
      v->workmul[noteno][octave+4] = mul;
    };
  };

  p = (int) ((long) strchr(anoctave, note) - (long) anoctave);
  if (temperament) {
    p = tscale[p];
    if (acc == '^') p = p + mul*accidental_size;
    if (acc == '_') p = p - mul*accidental_size;
    pitch4096 =  p + octave*octave_size + middle_c;
    pitch =  (SEMISIZE*128+pitch4096+SEMISIZE/2)/SEMISIZE-128;
    bend =  8192+4096*(pitch4096 - pitch*SEMISIZE)/SEMISIZE;
    bend = bend<0?0:(bend>16383?16383:bend);
   } else {
    p = scale[p];
    if (acc == '^') p = p + mul;
    if (acc == '_') p = p - mul;
    pitch = p + 12*octave + middle_c;
    bend = 8192; /* corresponds to zero bend */
    }
if (!microtone) *pitchbend = bend; /* don't override microtone */
return pitch; 
}



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;
  int bend_up,bend_down;
  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_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend_up);
  pitchdown = pitchof_b(down, v->basemap[(int)down - 'a'], 1, downoct, 0,&bend_down);
  bentpitch[notes] = active_pitchbend;
  addfeature(NOTE, pitch, n*4, m*(v->default_length)*5);
  marknotestart();
  bentpitch[notes] = bend_up;
  addfeature(NOTE, pitchup, n*4, m*(v->default_length)*5);
  bentpitch[notes] = active_pitchbend;
  addfeature(NOTE, pitch, n*4, m*(v->default_length)*5);
  bentpitch[notes] = bend_down;
  addfeature(NOTE, pitchdown, n*4, m*(v->default_length)*5);
  bentpitch[notes] = active_pitchbend;
  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;
  int bend;

  upoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  if (up == 'c') upoct = upoct + 1;
  pitchup = pitchof_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend);
  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) {  **bug** [SS] 2006-09-10 */
     if (i == 0) {
      marknotestart();
    };
    if (i%2 == 0) {
      bentpitch[notes] = bend;
      addfeature(NOTE, pitchup, a, b);
    } else {
      bentpitch[notes] = active_pitchbend;
      addfeature(NOTE, pitch, a, b);
    };
    i = i + 1;
  };
  marknoteend();
}



void makecut (mainpitch, shortpitch,mainbend,shortbend, n,m)
int mainpitch,shortpitch,mainbend,shortbend,n,m;
{
addfeature(GRACEON, 0, 0, 0);
bentpitch[notes] = shortbend;
addfeature(NOTE, shortpitch, 4,v->default_length);
addfeature(GRACEOFF, 0, 0, 0);
bentpitch[notes] = mainbend;
addfeature(NOTE, mainpitch, 4*n,m*v->default_length);
}


static void doornament(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";
  int nnorm,mnorm,nn;
  int bend_up,bend_down;

  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_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend_up);
  pitchdown = pitchof_b(down, v->basemap[(int)down - 'a'], 1, downoct, 0,&bend_down);
  marknotestart();
  /* normalize notelength to L:1/8 */
  /*  nnorm =n*8; [SS] 2008-06-14 */
  nnorm = n*(v->default_length);
  mnorm =m*(v->default_length);
  reduce(&nnorm,&mnorm);
  if (nnorm == 3 && mnorm == 1) /* dotted quarter note treated differently */
     {
     nn = n/3; /* in case L:1/16 or smaller */
     if(nn < 1) nn=1;
     bentpitch[notes] = active_pitchbend; /* [SS] 2006-11-3 */
     addfeature(NOTE, pitch, 4*nn,v->default_length);
     makecut(pitch,pitchup,active_pitchbend,bend_up,nn,m);
     makecut(pitch,pitchdown,active_pitchbend,bend_down,nn,m);
     }
  else makecut(pitch,pitchup,active_pitchbend,bend_up,n,m);
  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 num, denom;
  int octave;
  int pitch;
  int pitch_noacc;
  int dummy;

  if (v == NULL) {
    event_fatal_error("Internal error - no voice allocated");
  };
  octave = xoctave + v->octaveshift;
  num = n;
  denom = m;
  if (v->inchord) v->chordcount = v->chordcount + 1;
/*  if (tuplecount > 0 && !v->inchord) { [SS] 2008-06-13} */
  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)) {
      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));
  };

/* linear temperament support */
  if (v->drumchannel) pitch = barepitch(note,accidental,mult,octave);
  else if (nopropagate_accidentals == 1 ) 
    pitch = pitchof_b(note, accidental, mult, octave, 0,&active_pitchbend);
  else
    pitch = pitchof_b(note, accidental, mult, octave, 1,&active_pitchbend);
  pitch_noacc = pitchof_b(note, 0, 0, octave, 0,&dummy);

  if (decorators[FERMATA]) {
    if(fermata_fixed) addfract(&num,&denom,1,1);
    else num = num*2;
  };
  if (v->chordcount == 1) {
    v->chord_num = num*4;
    v->chord_denom = denom*(v->default_length);
  };
  decorators[ROLL] = decorators[ORNAMENT] = decorators[TRILL] = 0;//FIXME- I don't like it doing rolls!
  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 if (decorators[ORNAMENT]) {
        doornament(note, octave, num, denom, pitch);
      }
      else { 
        doroll(note, octave, num, denom, pitch);
      };
     }; /* end of else block for not in chord */
   } /* end of if block for ROLL,ORNAMENT,TRILL */
   else {
    if (decorators[STACCATO] || decorators[BREATH]) {
      if (v->inchord) {
        if (v->chordcount == 1) {
          addfeature(REST, pitch, num*4, denom*(v->default_length));
        };
	pitchline[notes] = pitch_noacc;
        bentpitch[notes] = active_pitchbend;
        addfeature(NOTE, pitch, num*4, denom*2*(v->default_length));
      } else {
	pitchline[notes] = pitch_noacc;
        bentpitch[notes] = active_pitchbend;
        addfeature(NOTE, pitch, num*4, denom*2*(v->default_length));
        marknotestart();
        addfeature(REST, pitch, num*4, denom*2*(v->default_length));
        marknoteend();
      };
    } else {
      pitchline[notes] = pitch_noacc;
      bentpitch[notes] = active_pitchbend;
      addfeature(NOTE, pitch, num*4, denom*(v->default_length));
      if (!v->inchord) {
        marknote();
      }; 
      if ((v->inslur) && (!v->ingrace)) {
        addfeature(SLUR_TIE, 0, 0, 0);
      };
    };
  };
}


void event_microtone(int dir, int a, int b)
{
int bend;
/* pitchwheel range +/- 2 semitones according to General MIDI
specification*/
/* resolution of 14bit -- order of bytes is inverted for pitchbend */
bend = dir*((int)(4096.0*a/b))+8192; /* sorry for the float! */
bend = bend<0?0:(bend>16383?16383:bend);
active_pitchbend = bend;
microtone=1;
}


void event_normal_tone()
{ 
/* event_specific("MIDI", "pitchbend 0 64"); [SS] 2006-09-30 */
microtone = 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 == '(') p = p+1; /* ignore leading ( [SS] 2005-03-19*/
  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, 0) - middle_c;
  i = 0;
  while ((i<9) && (*p != ' ') && (*p != '\0') && (*p != '(')
               && (*p != '/') && (*p != ')')) {
/* also ignore closing parentheses ')'  [SS] 2005-03-19*/
    name[i] = *p;
    i = i+1;
    p = p + 1;
  };
  inversion = -1;
  if (*p == '/') {
    p = p + 1;
    if ((*p >= 'A') && (*p <= 'G')) {
      note = (int)*p - 'A' + 'a';
      p = p + 1;
      p = get_accidental(p, &accidental);
      inversion = pitchof(note, accidental, 1, 0, 0) - middle_c;
    } else if ((*p >= 'a') && (*p <= 'g')) {
      note = (int)*p;
      p = p + 1;
      p = get_accidental(p, &accidental);
      inversion = pitchof(note, accidental, 1, 0, 0) - middle_c;
    } else {
      event_error(" / must be followed by A-G or a-g in gchord");
    };
  };
  name[i] = '\0';
  chordno = getchordnumber(name);
  if (chordno == 0) {
    char msg[200];

    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;
  };
  if ( !noGuitarChords )
    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;
  char midimsg[40];

  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;
/* add nofnop ... */
if (nofnop == 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,"crescendo(") == 0) || (strcmp(p,"<(") == 0) || 
      (strcmp(p,"crescendo)") == 0) || (strcmp(p,"<)") == 0)) {
          sprintf(midimsg,"beatmod %d",velocitychange);
          event_specific("MIDI", midimsg);
          done = 1;
   }

  if (
      (strcmp(p,"diminuendo)") == 0) || (strcmp(p,">)") == 0) || 
      (strcmp(p,"diminuendo(") == 0) || (strcmp(p,">(") == 0)) {
          sprintf(midimsg,"beatmod -%d",velocitychange);
          event_specific("MIDI", midimsg);
          done = 1;
   }

}; /* end nofnop */
  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 (strcmp(s, "fermata") == 0) {
    decorators_passback[FERMATA] =1;
    done = 1;
    };

  if (strcmp(s, "trill") == 0) {
    decorators_passback[TRILL] =1;
    done = 1;
    };


  if (strcmp(p, "arpeggio") == 0) {
    addfeature(ARPEGGIO, 0, 0, 0);
    done = 1;
  };

  if (strcmp(s, "breath") == 0) {
    decorators_passback[BREATH] =1;
    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 i,j;

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

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

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

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

void nondestructive_readstr(out, in, limit)
char out[];
char **in;
int limit;
/* copy across alphanumeric string */
{
  int i;
  i = 0;
  while ((isalpha(*(*in+i))) && (i < limit-1)) {
    out[i] = *(*in+i);
    i = i + 1;
  };
  out[i] = '\0';
}


static void dotie(j, xinchord,voiceno)
/* called in preprocessing stage to handle ties */
/* we need the voiceno in case a tie is broken by a */
/* voice switch.                                    */
int j, xinchord,voiceno;
{
  int tienote, place;
  int tietodo, done;
  int newbar;
  int lastnote, lasttie;
  int inchord;
  int tied_num, tied_denom;
  int localvoiceno;
  int samechord;
  char command[40];

  /* find note to be tied */
  samechord = 0;
  newbar =0;  /* if 1 it allows pitchline[] == pitchline[] test */
  if (xinchord) samechord = 1;
  tienote = j;
  localvoiceno = voiceno;
  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;

/* fix for tied microtone note */
    if (feature[tienote+1] == DYNAMIC) { 
      nondestructive_readstr(command, &atext[pitch[tienote+1]], 40);
      if (strcmp(command, "pitchbend") == 0) {
        /*printf("need to remove pitchbend following TNOTE\n"); */
        removefeature(tienote+1);
        j = j-1;
        }
    }

/* fix for tied microtone note inside a slur */
    if (feature[tienote+1] == SLUR_TIE && feature[tienote+2] == DYNAMIC) {
      nondestructive_readstr(command, &atext[pitch[tienote+2]], 40);
      if (strcmp(command, "pitchbend") == 0) {
        /*printf("need to remove pitchbend following TNOTE in slur\n"); */
        removefeature(tienote+2);
        j = j-1;
        }
    }

    num[j] = num[tienote];
    denom[j] = denom[tienote];
    place = j;
    tietodo = 1;
    lasttie = j;
    tied_num = num[tienote];
    tied_denom = denom[tienote];
    lastnote = -1;
    done = 0;
    while ((place < notes) && (tied_num >=0) && (done == 0)) {
      /*printf("%d %s   %d %d/%d ",place,featname[feature[place]],pitch[place],num[place],denom[place]); */
      switch (feature[place]) {
        case SINGLE_BAR:
        case BAR_REP:
        case REP_BAR:
          if (tietodo) newbar = 1;
          else newbar=0;
          break; 
        case NOTE:
          if(localvoiceno != voiceno) break;
          lastnote = place;
          if ((tied_num == 0) && (tietodo == 0)) {
            done = 1;
          };
          if (((pitchline[place] == pitchline[tienote] && newbar) || (pitch[place] == pitch[tienote]))
             && (tietodo == 1) && (samechord == 0)) {
            /* tie in note */
            if (tied_num != 0) {
              event_error("Time mismatch at tie");
            };
            tietodo = 0;
	    pitch[place] = pitch[tienote]; /* in case accidentals did not
					      propagate                   */
            /* 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(localvoiceno != voiceno) break;
          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(localvoiceno != voiceno) break;
          if (lastnote == -1) {
            event_error("Bad tie: possibly two ties in a row");
          } else {
            if (pitch[lastnote] == pitch[tienote] && samechord == 0) {
              lasttie = place;
              tietodo = 1;
              if (inchord) samechord = 1;
            };
          };
          break;
        case CHORDON:
          if(localvoiceno != voiceno) break;
          inchord = 1;
          break;
        case CHORDOFF:
        case CHORDOFFEX:
          samechord = 0;
          if(localvoiceno != voiceno) break;
          inchord = 0;
          /* subtract time from tied time */
          addfract(&tied_num, &tied_denom, -num[place], denom[place]);
          break;
        case VOICE:
          localvoiceno = pitch[place];
        default:
          break;
      };
      /*printf("tied_num = %d done = %d inchord = %d\n",tied_num, done, inchord);*/
      place = place + 1;
    };
    if (tietodo == 1) {
      event_error("Could not find note to be tied");
    };
  };
/*printf("dotie finished\n");*/ 
}

static void fix_enclosed_note_lengths(int from, int end) 
{
/* in event that the chord length was set outside [], */
/* we must go back and adjust the note length of each note */
/* inside the chord.                                   */

int j;
for (j = from; j < end; j++)
  {
  if (feature[j] == NOTE || feature[j] == TNOTE) 
    {
      num[j] = num[end];
      denom[j] =denom[end]; 
    }
  }
}


static int patchup_chordtie(int chordstart,int chordend)
{
int i,tieloc;
for (i=chordend;i>=chordstart;i--) {
	if(feature[i]==NOTE && feature[i+1] != TIE) {
             insertfeature(TIE,0,0,0,i+1);
	     tieloc = i+1;
	}
     }
 return tieloc;
}


static void tiefix()
/* connect up tied notes and cleans up the */
/* note lengths in the chords (eg [ace]3 ) */
{
  int j;
  int inchord;
  int chord_num, chord_denom;
  int chord_start,chord_end;
  int voiceno;

  j = 0;
  inchord = 0;
  voiceno = 1;
  while (j<notes) {
    switch (feature[j]) {
    case CHORDON:
      inchord = 1;
      chord_num = -1;
      chord_start = j;
      j = j + 1;
      break;
    case CHORDOFFEX:
      inchord = 0;
      chord_end=j;
      j = j + 1;
      break;
    case CHORDOFF:
      if (!((!inchord) || (chord_num == -1))) {
        num[j] = chord_num; 
        denom[j] = chord_denom; 
      };
      inchord = 0;
      chord_end=j;
      j = j + 1;
      break;
    case NOTE:
      if ((inchord) && (chord_num == -1)) {
        chord_num = num[j];
        chord_denom = denom[j];
        /* use note length in num,denom as chord length */
        /* if chord length is not set outside []        */
      };
      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:
#if 0
      if (chord_end+1 == j) { /* did a TIE connect with a chord */
	      // removefeature(j);
	       patchup and backtrack/
	      j = patchup_chordtie(chord_start,chord_end);
	      inchord=1;
              }
#endif
      dotie(j, inchord,voiceno);
      j = j + 1;
      break;
    case LINENUM:
      lineno = pitch[j];
      j = j + 1;
      break;
    case VOICE:
      voiceno = pitch[j];
    default:
      j = j + 1;
      break;
    };
  };
}

static void applygrace_orig(int);
static void applygrace_new(int);

static void applygrace(place)
int place;
{
if (gfact_method)  applygrace_orig(place); 
else applygrace_new(place);
}


static void applygrace_orig(place)
int place;
/* assign lengths to grace notes before generating MIDI */
/* This version adjusts the length of the grace notes
 * based on the length of the following note, the
 * number of the notes in the group of grace notes
 * and the desired fraction, gfact_num/gfact_denom.
 * This does not sound very natural.
 */
{
  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) {
        hostnoteend = j-1;
      };
      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 applygrace_new(place)
int place;
/* assign lengths to grace notes before generating MIDI */
/* In this version each grace note has a predetermined
 * length, eg, (1/64 th note) and the total length of
 * the group of grace notes is stolen from the following
 * note. If the length of the following note is note
 * long enough to accommodate the group of grace notes,
 * then all the grace notes are given a length of 0.
 */
{
  int start, end, p;
  int fact_num, fact_denom;
  int grace_num, grace_denom;
  int j;
  int nextinchord;
  int hostnotestart, hostnoteend;
  int  adjusted_num, adjusted_den;

  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) {
        hostnoteend = j-1;
      };
      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;
    };

/* new stuff starts here [SS] 2004/06/11 */

/* is the following note long enough */
   p = hostnotestart;
   adjusted_num = num[p]*grace_denom*gfact_denom - denom[p]*grace_num;
   adjusted_den = denom[p]*grace_denom*gfact_denom;
   if (adjusted_den <=0.0) /* not long enough*/
      {
      p = start;
      while (p <= end)
        { 
        lenmul(p,0,1);
	p = p+1;
	}
      return;
      }

    /* adjust host note or notes */
    p = hostnotestart;
    while (p <= hostnoteend) {
      if ((feature[p] == NOTE) || (feature[p] == REST) || 
          (feature[p] == CHORDOFF)) {
        num[p] = adjusted_num;
        denom[p] = adjusted_den;
        reduce(&num[p], &denom[p]);
      };
      p = p + 1;
    };

    fact_num = 1;
    fact_denom =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_method = pitch[j];
      gfact_num =  num[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;
  int depth;
  int voiceno, indexno;

  depth = splitdepth;

  newtype = type;
  if ((type == THIN_THICK) || (type == THICK_THIN)) {
    newtype = DOUBLE_BAR;
  };
  addfeature(newtype, 0, 0, 0);
  copymap(v);
  zerobar();
  if (strlen(replist) > 0) {
    event_playonrep(replist);
  };

if (splitdepth > 0) {
  /* we encountered the repeat while in a split voice; the
     repeat goes there but we need to also copy it to all
     its ancestors.
  */
  if (type != SINGLE_BAR)  recurse_back_and_change_bar (type);
  else recurse_back_to_original_voice();
  }
/* depth == 0 implies the repeat symbol was encountered while
   we are not in a split voice but we need to put repeat symbol
   in all split voices
*/
else
 {
 voiceno = v->voiceno;
 indexno = v->indexno;
 while (v->tosplitno != -1)
     { 
      v = getvoicecontext(v->tosplitno);
      splitdepth++;
      addfeature(VOICE, v->indexno, 0, 0);
      sync_voice (v,0,0);
      /*addfeature(newtype,0 ,0, 0); sync_voice does this*/
      if (strlen(replist) > 0) event_playonrep(replist);
     }
 if (v->fromsplitno != -1 || splitdepth >0) recurse_back_to_original_voice();
 }
}


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;
    event_warning("replacing :| with double bar ||");
    break;
  case DOUBLE_REP:
    feature[j] = BAR_REP;
    event_warning("replacing :: with |:");
    break;
  default:
    break;
  };
}

static void placeendrep(j)
/* patch up missing repeat */
int j;
{
  if (quiet == -1) 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;
    event_warning("replacing with double repeat (::)");
    break;
  default:
    event_error("Internal error - please report");
    break;
  };
}

static void placestartrep(j)
/* patch up missing repeat */
int j;
{
  if (quiet == -1) 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:
    if (quiet == -1) event_warning("replacing |: with double repeat (::)");
    feature[j] = DOUBLE_REP;
    break;
  case BAR_REP:
    if (quiet == -1) event_error("Too many end repeats");
    break;
  case DOUBLE_REP:
    if (quiet == -1) 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;   /* = 1 after |: or ::  otherwise 0*/
  int use_next; /* if 1 set next bar line as ref_point */
  int nplays; /* counts PLAY_ON_REP associated with a BAR_REP*/

  expect_repeat = 0; 
  use_next = 0;
  nplays=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 |:  .. |: encountered. Report error and change second |: to :: */
      if (expect_repeat) {
          if (quiet == -1) event_error(" found another |: after a |:");
          placeendrep(j);
      };
      expect_repeat = 1;
      use_next = 0;
      break;
    case REP_BAR:                /* :| */
/* if :| encountered before |: place  "|:" at reference point */
       if ((!expect_repeat) && (nplays < 1)) {
          placestartrep(rep_point);
        };
      expect_repeat = 0; 
      rep_point = j;
      use_next = 0;
      break;

    case PLAY_ON_REP:         /*  [1 or [2 or [1,3 etc  */
       if(nplays == 0 && !expect_repeat) {
           if (quiet == -1) event_error(" found [1 or like before |:");
           placestartrep(rep_point);
           }
       nplays++;
       break;

    case DOUBLE_REP:           /*  ::  */
      if (!expect_repeat) {
          if (quiet == -1) event_error(" found :: before |:");
          placestartrep(rep_point);
       };
      expect_repeat = 1;
      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;
  global.keyset = 0;
  voicecount = 0;
  head = NULL;
  for (j=0;j<64;j++) vaddr[j]=NULL; 
  v = NULL;
  got_titlename = 0;
  time_num = 4;
  time_denom = 4;
  mtime_num = 4;
  mtime_denom = 4;
  timesigset = 0;
  barchecking = 1;
  global.default_length = -1;
  event_tempo(default_tempo, 1, 4, 0, NULL, NULL);
  notes = 0;
  ntexts = 0;
  gfact_num = 1;
  gfact_denom = 4;
  hornpipe = 0;
  karaoke = 0;
  numsplits = 0;
  retain_accidentals = default_retain_accidentals;
  fermata_fixed = default_fermata_fixed;
  if (ratio_standard == -1) {
    ratio_a = default_ratio_a;
    ratio_b = default_ratio_b;
  } else {
     ratio_a = 2;
     ratio_b = 6;
   }
  wcount = 0;
  parts = -1;
  middle_c = default_middle_c;
  temperament=0;
  for (j=0; j<26; j++) {
    part_start[j] = -1;
  };
  headerpartlabel = 0;
  initvstring(&part);
  for (j=0; j<16;j++) {
    channels[j] = 0;
  };
  set_gchords("z");
  gchordvoice = 0;
  set_drums("z");
  drumvoice = 0;
  wordvoice = 0;
}

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");
  };
  if ((time_num == 12) && (time_denom == 8)) {
    set_gchords("fzcfzcfzcfzc");
  };
}

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;
  struct voicecontext *p;

  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;
    };
  };
/* ensure default_length is defined for all voices */
  p = head;
  while ((p != NULL)) {
    if (p->default_length == -1) p->default_length=global.default_length;
    p = p->next;
  };

  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;
  voicesused = 0;
}

void event_key(sharps, s, modeindex, modmap, modmul, gotkey, gotclef, clefname,
          octave, transpose, gotoctave, gottranspose)
/* handles a K: field */
int sharps; /* sharps is number of sharps in key signature */
int modeindex; /* 0 major, 1,2,3 minor, 4 locrian, etc.  */
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;
{
  int minor;
  minor =0;
  if (modeindex >0 && modeindex <4) minor = 1;
  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) {
        addfeature(GTRANSPOSE, transpose, 0, 0);
      };
      setmap(sharps, global.basemap, global.basemul);
      altermap(&global, modmap, modmul);
      global.keyset=1;
      copymap(&global);
      sf = sharps;
      mi = minor;
      headerprocess();

      v = getvoicecontext(1);
    };
    if (gotoctave) {
      event_octave(octave,0);
    };
  };
}

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

  complete_all_split_voices ();
  clearvoicecontexts();
  init_drum_map();
  if (!pastheader) {
    event_error("No valid 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) && (dronevoice==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 (dronevoice != 0) {
	dronetrack = 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");
      };
#if !IPHONE
      printf("writing MIDI file %s\n", outname);
#endif
      Mf_putc = myputc;
      Mf_writetrack = writetrack;
      header_time_num = time_num;
      header_time_denom = time_denom;
      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) {
    print_voicecodes();
    finishfile();
    parseroff();
    dotune = 0;
  };
}

void event_refno(n)
/* handles an X: field (which indicates the start of a tune) */
int n;
{
  char numstr[23]; /* Big enough for a 64-bit int! */
  char newname[256];

  started_parsing = 1;
  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;
     
// JH-commented this out because it's a memory leak - head is set to NULL in startfile
//      v = newvoice(1);
//      head = v;
//      vaddr[0] = v;
//      vaddr[v->indexno] = v;

    pastheader = 0;
    if (userfilename == 0) {
      if (outname != NULL) {
        free(outname);
      };
      sprintf(numstr, "%d", n);
      if ( (int) strlen(numstr) > namelimit - 1) {
        numstr[namelimit - 1] = '\0';
      };
      if ((int) (strlen(outbase) + strlen(numstr)) > namelimit) {
        strncpy(newname, outbase, namelimit - strlen(numstr));
        strcpy(&newname[namelimit - strlen(numstr)], numstr);
        strcpy(&newname[strlen(newname)], ".mid");
      } else {
        sprintf(newname, "%s%s.mid", outbase, numstr);
      };
      outname = addstring(newname);
      /* 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(pitchline);
  free(bentpitch);
  free(num);
  free(denom);
  free(feature);
  free(words);
  free(outname);
  free(outbase);
}


int main(argc,argv)
int argc;
char *argv[];
{
  char *filename;
  int i;
  oldchordconvention = 0; /* for handling +..+ chords */

  for (i=0;i<DECSIZE;i++) decorators_passback[i]=0;
  for (i=0;i<64;i++) dependent_voice[i]=0;

  event_init(argc, argv, &filename);
  if (argc < 2) {
    /* printf("argc = %d\n", argc); */
  } else {
    init_abbreviations();
    parsefile(filename);
    free_abbreviations();
  };
  return(0);
}

