/*
  matchsup.c  (abcmatch support functions)
  Seymour Shlien

  


 * originally store.c
 * abc2midi - program to convert abc files to MIDI files.
 * Copyright (C) 1999 James Allwright
 * e-mail: J.R.Allwright@westminster.ac.uk

store.c was adapted for the purpose of analyzing the contents
of abc tunes. The code is much the same except many functions
related to the generation of midi files has been trimmed off.
In particular: links to the code in genmidi.c and queue.c are gone.
All the gchord stuff is removed. There are options to suppress
all warning and error messages. Certain variables are no longer
extern's (parts, partno, bar_num, global_transpose etc.).
Output file generation stuff is gone. A new variable xrefno is
added to pass the reference number to abcmatch. Event_init has
been moved to the main program abcmatch.c and maxnotes has
been raised to 3000 since there seems to be a problem with
the autoextend procedure.  Some DEBUG statements were
added to addfeature - (they can probably be removed). Support
for text features and comments are gone. All the karaoke stuff
is gone. Tempo stuff, slurs, grace notes, decorations, hornpipe
indications  are all ignored. Event_note was changed to ignore
trills and rolls.  When a [chord] is included, only the highest
note is extracted from the chord. For debugging, a new function
print_feature_list was introduced.etc.

Essentially for comparing abc files, we want to ignore repeats,
grace notes, staccato indications and midi indications. We are
only interested in comparing the tunes. The main output 
which is used is the feature[],pitch,num[],denom[] list which
is used by abcmatch to create a new representation of the abc
file.

Some abc files with interleaved voices will not be treated
propertly with this program. In general parts, repeats and
voice indications are ignored.
 

 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 */


#define XTEN1 1

#include "abc.h"
#include "parseabc.h"
#include "parser2.h"
#include <stdio.h>
#include <stdlib.h>

#ifdef  WIN32
#include <string.h>  /* [gjg] 2012-02-01  Visual C++ 2010 Express compatability */
#endif

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

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

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

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


/*#define DEBUG*/


/* global variables grouped roughly by function */

FILE *fp;

programname fileprogram = ABCMATCH;


/* 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 foundtitle; /* flag for capturing on the first title */

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

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

int note_unit_length=8;

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

int verbose = 0;
int nowarn=1;
int noerror=1;
int xmatch;
int sf, mi;

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

int voicesused;

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

/* output file generation */
int check;
int ntracks;

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

/* generating MIDI output */
int middle_c;
extern int channels[MAXCHANS + 3];

int global_transpose;

int additive;
int gfact_num, gfact_denom;

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

extern int intune; /* signals to parsetune that tune is finished */
extern char titlename[48]; /* stores title of tune */
extern char keysignature[16];

/* Many of these functions have been retained in order to link with parseabc.
As I have been forced to also modifiy parseabc, now called abcparse, these
functions can also be removed eventually.
 */

char *featname[] = {
"SINGLE_BAR", "DOUBLE_BAR", "BAR_REP", "REP_BAR",
"PLAY_ON_REP", "REP1", "REP2", "BAR1",
"REP_BAR2", "DOUBLE_REP", "THICK_THIN", "THIN_THICK",
"PART", "TEMPO", "TIME", "KEY",
"REST", "TUPLE", "NOTE", "NONOTE",
"OLDTIE", "TEXT", "SLUR_ON", "SLUR_OFF",
"TIE", "CLOSE_TIE", "TITLE", "CHANNEL",
"TRANSPOSE", "RTRANSPOSE", "GTRANSPOSE", "GRACEON",
"GRACEOFF", "SETGRACE", "SETC", "SETTRIM", "GCHORD",
"GCHORDON", "GCHORDOFF", "VOICE", "CHORDON",
"CHORDOFF", "CHORDOFFEX", "DRUMON", "DRUMOFF",
"DRONEON", "DRONEOFF", "SLUR_TIE", "TNOTE",
"LT", "GT", "DYNAMIC", "LINENUM",
"MUSICLINE", "MUSICSTOP", "WORDLINE", "WORDSTOP",
"INSTRUCTION", "NOBEAM", "CHORDNOTE", "CLEF",
"PRINTLINE", "NEWPAGE", "LEFT_TEXT", "CENTRE_TEXT",
"VSKIP", "COPYRIGHT", "COMPOSER", "ARPEGGIO",
"SPLITVOICE", "META", "PEDAL_ON", "PEDAL_OFF", "EFFECT"
};



void event_info (place)
char * place;
{
}

void event_gchord (chord)
char * chord;
{
}

void event_slur (t)
int t;
{
}


void event_instruction (s)
char *s;
{
}


void event_reserved (p)
char p;
{
}

int bar_num, bar_denom, barno, barsize;
int b_num,b_denom;

void reduce(a, b)
/* elimate common factors in fraction a/b */
int *a, *b;
{
  int sign;
  int t, n, m;

  if (*a < 0) {
    sign = -1;
    *a = -*a;
  } else {
    sign = 1;
  };
  /* find HCF using Euclid's algorithm */
  if (*a > *b) {
    n = *a;
    m = *b;
  } else {
    n = *b;
    m = *a;
  };
  while (m != 0) {
    t = n % m;
    n = m;
    m = t;
  };
  *a = (*a/n)*sign;
  *b = *b/n;
}

void addunits(a, b)
/* add a/b to the count of units in the bar */
int a, b;
{
  bar_num = bar_num*(b*b_denom) + (a*b_num)*bar_denom;
  bar_denom = bar_denom * (b*b_denom);
  reduce(&bar_num, &bar_denom);
}


void set_meter(n, m)
/* set up variables associated with meter */
int n, m;
{
  /* set up barsize */
  barsize = n;
  if (barsize % 3 == 0) {
    beat = 3;
  } else {
    if (barsize % 2 == 0) {
      beat = 2;
    } else {
      beat = barsize;
    };
  };
  /* correction factor to make sure we count in the right units */
  if (m > 4) {
    b_num = m/4;
    b_denom = 1;
  } else {
   b_num = 1;
   b_denom = 4/m;
  };
}

int dummydecorator[DECSIZE]; /* used in event_chord */


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

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

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

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

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

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




void event_text(s)
/* text found in abc file */
char *s;
{
}


void event_specific (package, s)
char *package, *s;
{
}


void event_x_reserved(p)
/* reserved character H-Z found in abc file */
char p;
{
}

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

/* [SS] 2015-03-23 */
void event_start_extended_overlay()
{
event_error("extended overlay not implemented in abcmatch");
}

void event_stop_extended_overlay()
{
event_error("extended overlay not implemented in abcmatch");
}


void event_split_voice()
{
}



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

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

void event_error(s)
/* generic error handler */
char *s;
{
if (noerror) return;
#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;
{
if (nowarn) return;
#ifdef NOFTELL
  extern int nullpass;

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

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

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


static 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]);
  };
#ifdef DEBUG
  if ((f == NOTE) || (f == REST)) {
    float  fract = (float) num[notes]/ (float) denom[notes];  /* [gjg] 2012-02-01 */
    int  length = fract * 12.0 +0.1;  /* [gjg] 2012-02-01 */
    printf("%2d %2d %2d %2d\n",pitch[notes],num[notes],denom[notes],length);
    }
  if (f == TIME) printf("time signature = %d/%d\n",n,d);
#endif 
  notes = notes + 1;
  if (notes >= maxnotes) {
    maxnotes = autoextend(maxnotes);
  };
}

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

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

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

static void textfeature(type, s)
/* called while parsing abc - stores an item which requires an */
/* associared string */
int type;
char* s;
{
}

void event_comment(s)
/* comment found in abc */
char *s;
{
}


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

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

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

        p = f;
        skipspace(&p);
/******** if ((strncmp(p, "Hornpipe", 8) == 0) ||
            (strncmp(p, "hornpipe", 8) == 0)) {
          hornpipe = 1;
        };
********/
      };
      break;
    case 'T':
     if (foundtitle == 0)  strncpy(titlename,f,46);
     foundtitle = 1; /* [SS] 2014-01-01 */
      break;
    default:
      {
      };
    };
  };
}

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

/* [SS] 2014-08-16 */
void appendfield (morewords)
char *morewords;
{
printf("appendfield not implemented here\n");
}


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 read_spec(spec, part)
/* converts a P: field to a list of part labels */
/* e.g. P:A(AB)3(CD)2 becomes P:AABABABCDCD */
/* A '+' indicates 'additive' behaviour (a part may include repeats).  */
/* A '-' indicates 'non-additive' behaviour (repeat marks in the music */
/* are ignored and only repeats implied by the part order statement    */
/* are played).  */
char spec[];
struct vstring* part;
{
}

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_octave(int, int);

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);
    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;
{
  note_unit_length = 8;
  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;
{
}

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;
{
}


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;

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

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

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

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

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

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

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

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

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

static void slurtotie()
/* converts a pair of identical slurred notes to tied notes */
{
}

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

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

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

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

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

void event_broken(type, mult)
/* handles > >> >>> < << <<< in the abc */
int type, mult;
{
  if (v->inchord) {
    event_error("Broken rhythm not allowed in chord");
  } else {
    if (v->ingrace) {
      event_error("Broken rhythm not allowed in grace notes");
    } else {
      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);
  };
}

void event_ignore () { };  /* [SS] 2018-12-21 */


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 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 (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,c)
/* multiple bar rest of n/m in the abc */
/* we check for m == 1 in the parser */
int n, m;
char c; /* [SS] 2017-04-19 to distinguish X from Z in abc2abc */
{
  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 {
    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 */
{
  if (!v->inchord) {
    event_error("Chord already finished");
  } else {

    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, chord_n*4, chord_m*v->default_length);

    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)
/* 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 == ' ') {
    acc = v->workmap[noteno];
    mul = v->workmul[noteno];
  } else {
    if ((retain_accidentals) && (propogate_accs)) {
      v->workmap[noteno] = acc;
      v->workmul[noteno] = mul;
    };
  };
  if (acc == '^') p = p + mul;
  if (acc == '_') p = p - mul;
  return p + 12*octave + middle_c;
}



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


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

  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) {
    num = num * tfact_num;
    denom = denom * tfact_denom;
    if (tnote_num == 0) {
      tnote_num = num;
      tnote_denom = denom;
    } else {
      if (tnote_num * denom != num * tnote_denom) {
        if (!specialtuple) {
          event_warning("Different length notes in tuple");
        };
      };
    };
    if ((!gracenotes) &&
        ((!v->inchord) || ((v->inchord) && (v->chordcount == 1)))) {
      tuplecount = tuplecount - 1;
    };
  };
  if ((!v->ingrace) && (!v->inchord)) {
    hornp(num, denom*(v->default_length));
  } else {
    last_num = 3; /* hornpiping (>) cannot follow chord or grace notes */
  };
  if ((!v->ingrace) && ((!v->inchord)||(v->chordcount==1))) {
    addunits(num, denom*(v->default_length));
  };
  pitch = pitchof(note, accidental, mult, octave, 1);
  pitch_noacc = pitchof(note,0,0,octave,0);
  if (decorators[FERMATA]) {
    num = num*2;
  };
  if (v->chordcount == 1) {
    v->chord_num = num*4;
    v->chord_denom = denom*(v->default_length);
  };

   pitchline[notes] = pitch_noacc;
   addfeature(NOTE, pitch, num*4, denom*(v->default_length));
   marknote();
}

void event_microtone(int dir, int a, int b)
{
}

void event_normal_tone()
{
}



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;
{
}

void event_handle_instruction(s)
/* handler for ! ! instructions */
/* does ppp pp p mp mf f ff fff */
/* also does !drum! and !nodrum! */
char* s;
{
}

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

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

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

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

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

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

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

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

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


static void dotie(j, xinchord,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 lastnote, lasttie;
  int inchord;
  int tied_num, tied_denom;
  int localvoiceno;
  int samechord;

  /* find note to be tied */
  samechord = 0;
  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;
    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 NOTE:
          if(localvoiceno != voiceno) break;
          lastnote = place;
          if ((tied_num == 0) && (tietodo == 0)) {
            done = 1;
          };
          if ((pitchline[place] == pitchline[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 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:
      /*dotie(j, inchord,voiceno); [SS] 2013-04-04 */
      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 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;
#ifdef DEBUG
  printf("bar line\n");
#endif
  newtype = type;
  if ((type == THIN_THICK) || (type == THICK_THIN)) {
    newtype = DOUBLE_BAR;
  };
  if (type == BAR1) {
    newtype = SINGLE_BAR;
  };
  if (type == REP_BAR2) {
    newtype = REP_BAR;
  };
  addfeature(newtype, 0, 0, 0);
  copymap(v);
  zerobar();
  if (strlen(replist) > 0) {
    event_playonrep(replist);
  };
/*
  if (type == BAR1) {
    addfeature(PLAY_ON_REP, 0, 0, 1);
  };
  if (type == REP_BAR2) {
    addfeature(PLAY_ON_REP, 0, 0, 2);
  };
*/
}




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

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


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

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

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

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

void event_key(sharps, s, modeindex, modmap, modmul, modmicrotone, gotkey, gotclef, clefname,
          octave, transpose, gotoctave, gottranspose, explict)
/* 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) */
struct fraction modmicrotone[7]; /* [SS] 2014-01-06 */
int gotkey, gotclef;
int octave, transpose, gotoctave, gottranspose;
int explict;
char* clefname;
{
  int minor;
  strncpy(keysignature,s,16);
  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) {
        global_transpose = transpose;
      };
      setmap(sharps, global.basemap, global.basemul);
      altermap(&global, modmap, modmul);
      copymap(&global);
      sf = sharps;
      mi = minor;
      headerprocess();
      v = newvoice(1);
      head = v;
    };
    if (gotoctave) {
      event_octave(octave,0);
    };
  };
}



void print_feature_list ()
{
int i,length;
float fract;
printf("feature list \n");
for (i=0;i<notes;i++) {
  printf("%d %d %s %d %d %d",i,feature[i],featname[feature[i]],pitch[i],num[i],denom[i]);
  if (feature[i] == NOTE || feature[i] == TNOTE || feature[i] == REST) {
    fract = (float) num[i]/ (float) denom[i];
    length = (int) (fract * 12.0 +0.1);
    printf(" %d",length);
    }
  printf("\n");
  }
}

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


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


    if (parts > -1) {
      addfeature(PART, ' ', 0, 0);
    };
    if (headerpartlabel == 1) {
      event_error("P: field in header should go after K: field");
    };

    tiefix();


    };
    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) {
    intune = 0;
    finishfile();
    parseroff();
    dotune = 0;
  };
}

void event_refno(n)
/* handles an X: field (which indicates the start of a tune) */
int n;
{
  foundtitle = 0; /* [SS] 2014-01-10 */
  if (dotune) {
    finishfile();
    parseroff();
    dotune = 0;
    intune = 0; /* [SS] 2013-03-20 */
  };
  if (verbose) {
    printf("Reference X: %d\n", n);
  };
  if ((n == xmatch) || (xmatch == 0) || (xmatch == -1)) {
    parseron();
    dotune = 1;
    pastheader = 0;
    xrefno = n;
    startfile();
    return;
  };
}

void event_eof()
/* end of abc file encountered */
{
  if (dotune) {
    dotune = 0;
    parseroff();
    finishfile();
  };
  if (verbose) {
    printf("End of File reached\n");
  };
}


void free_feature_representation ()
{
  free(pitch);
  free(pitchline);
  free(num);
  free(denom);
  free(feature);
  free(words);
}



void dumpfeat (int from, int to)
{
int i,j;
for (i=from;i<=to;i++)
  {
  j = feature[i];
  if (j<0 || j>73) printf("illegal feature[%d] = %d\n",i,j); /* [SS] 2012-11-25 */
  else printf("%d %s   %d %d %d %d \n",i,featname[j],pitch[i],num[i],denom[i]);
  }
}


