Logo Search packages:      
Sourcecode: abcmidi version File versions

store.c

/*
 * 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 "1.56 January 1 2005"
/* 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

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

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

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

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

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

/* global variables grouped roughly by function */

FILE *fp;

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

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

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

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

/* general purpose storage structure */
int maxnotes;
int *pitch, *num, *denom;
featuretype *feature;
int *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 assume_repeat_warning = -1; /* if not -1 Assuming rest warning is */
                              /* suppressed.                        */

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


extern long writetrack();

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->keyset = global.keyset;
  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;
  int i;

  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];
      p->workmap[i] = global.workmap[i];
      p->workmul[i] = global.workmul[i];
      };
    }
  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;
  }

  maxnotes = 500;
  /* allocate space for notes */
  pitch = checkmalloc(maxnotes*sizeof(int));
  num = checkmalloc(maxnotes*sizeof(int));
  denom = checkmalloc(maxnotes*sizeof(int));
  feature = (featuretype*) checkmalloc(maxnotes*sizeof(featuretype));
  pitchline = checkmalloc(maxnotes*sizeof(int));

  /* 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]\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("        -NAR suppress assuming repeat warning\n");
    printf("        -Q default tempo (quarter notes/minute)\n");
    printf("        -NFNP don't process !p! or !f!-like fields\n");
    printf(" The default action is to write a MIDI file for each abc tune\n");
    printf(" with the filename <stem>N.mid, where <stem> is the filestem\n");
    printf(" of the abc file and N is the tune reference number. If the -o\n");
    printf(" option is used, only one file is written. This is the tune\n");
    printf(" specified by the reference number or, if no reference number\n");
    printf(" is given, the first tune in the file.\n");
    exit(0);
  } else {
    xmatch = 0;
    if ((argc >= 3) && (isdigit(*argv[2]))) {
      xmatch = readnumf(argv[2]);
    };
    *filename = argv[1];
    outbase = addstring(argv[1]);
    for (j = 0; j<strlen(outbase); j++) {
      if (outbase[j] == '.') outbase[j] = '\0';
    };
  };
  /* look for 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);
  assume_repeat_warning  = getarg("-NAR", 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 */
  if (strlen(s) > 199) s[199] = '\0';
  sprintf(msg, "Ignoring text: %s", s);
  event_warning(msg);
}

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

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

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

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

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

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

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

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

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

static int autoextend(maxnotes)
/* increase the number of abc elements the program can cope with */
int maxnotes;
{
  int newlimit;
  int *ptr,*ptr2;
  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));
  for(i=0;i<maxnotes;i++){
    ptr[i] = pitch[i];
    ptr2[i] = pitchline[i];
  };
  free(pitch);
  free(pitchline);
  pitch = ptr;
  pitchline = ptr2;
  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 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];
    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;
}

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] = pitch[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(type, s)
/* called while parsing abc - stores an item which requires an */
/* associared string */
int type;
char* s;
{
  atext[ntexts] = addstring(s);
  addfeature(type, ntexts, 0, 0);
  ntexts = ntexts + 1;
  if (ntexts >= maxtexts) {
    maxtexts = textextend(maxtexts, &atext);
  };
}

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

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

  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;
      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, "ratio") == 0) {
      int a, b;

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

      skipspace(&p);
      a = readnump(&p);
      if (*p != '/') {
        event_error("Need / in MIDI grace command");
      } else {
        p = p + 1;
      };
      b = readnump(&p);
      if ((a < 1) || (b < 1) || (a >= b)) {
        sprintf(msg, "%d/%d is not a suitable fraction", a, b);
        event_error(msg);
      } else {
        if (pastheader) {
          addfeature(SETGRACE, 1, a, b);
        } else {
          gfact_num = a;
          gfact_denom = b;
        };
      };
      done = 1;
    };

    if(strcmp(command,"gracedivider") == 0) {
      int b;
      char msg[40];
      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, "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,"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 (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++;
            }
            *ptr = '\0';
            textfeature(COPYRIGHT, msg);
        }
        else
        {
           strcpy(msg, "%");
           strcat(msg, package);
           strcat(msg, s);
           event_comment(msg);
        }
      }
    if (done == 0)  
    {
    strcpy(msg, "%");
/* guard against buffer overflow - security risk */
    if (strlen(package) > 200) {
       event_warning("event_specific: possible buffer overflow");
       return;
       }
    strcat(msg, package);
    bytesleft = 200-strlen(msg);;
    if(bytesleft < 0) return;
    if (strlen(s)>bytesleft) {
       event_warning("event_specific: possible buffer overflow");
       return;
       }
    strcat(msg, s);
    event_comment(msg);
    }
  }
}

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

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

void 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(k, f)
/* Handles R: T: and any other field not handled elsewhere */
/* Added code to handle C: field. */
char k;
char *f;
{
  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,gotclef,gotoctave,gottranspose,gotname,
            clefname,octave,transpose,namestring)
/* handles a V: field in the abc */
int n;
char *s;
int gotclef,gotoctave,gottranspose,gotname;
char *clefname;
int octave,transpose;
char *namestring;
{
  if (pastheader || XTEN1) {
    voicesused = 1;
    if (pastheader)  checkbreak();
    v = getvoicecontext(n);
    addfeature(VOICE, v->indexno, 0, 0);
    if (gotoctave) {
      event_octave(octave,1);
    };
    if (gottranspose) {
      addfeature(TRANSPOSE, 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);
  };
}

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 (t == 1) {
    addfeature(SLUR_ON, 0, 0, 0);
    v->inslur = 1;
  };
}

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


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

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

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

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

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

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

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

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

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

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

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



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

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

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

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


void event_chordoff(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 doroll(note, octave, n, m, pitch)
/* applies a roll to a note */
char note;
int octave, n, m;
int pitch;
{
  char up, down;
  int t;
  int upoct, downoct, pitchup, pitchdown;
  char *anoctave = "cdefgab";

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

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

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


/******************************
void makecut (mainpitch, shortpitch, n,m)
int mainpitch,shortpitch,n,m;
{
int adjusted_num,adjusted_den;
adjusted_num = n*gfact_denom -m;
adjusted_den = m*gfact_denom;
if (adjusted_den <0) 
  addfeature(NOTE, mainpitch, n, m);
else {
  addfeature(NOTE, shortpitch, 4,gfact_denom*(v->default_length));
  reduce(&adjusted_num,&adjusted_den);
  addfeature(NOTE, mainpitch,4*adjusted_num,adjusted_den*(v->default_length));
  }
}
*****************************/

void makecut (mainpitch, shortpitch, n,m)
int mainpitch,shortpitch,n,m;
{
addfeature(GRACEON, 0, 0, 0);
addfeature(NOTE, shortpitch, 4,v->default_length);
addfeature(GRACEOFF, 0, 0, 0);
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;

  upoct = octave;
  downoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  down = *(anoctave + ((t+6) % 7));
  if (up == 'c') upoct = upoct + 1;
  if (down == 'b') downoct = downoct - 1;
  pitchup = pitchof(up, v->basemap[(int)up - 'a'], 1, upoct, 0);
  pitchdown = pitchof(down, v->basemap[(int)down - 'a'], 1, downoct, 0);
  marknotestart();
  /* normalize notelength to L:1/8 */
  nnorm =n*8;
  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;
     addfeature(NOTE, pitch, 4*nn,v->default_length);
     makecut(pitch,pitchup,nn,m);
     makecut(pitch,pitchdown,nn,m);
     }
  else makecut(pitch,pitchup,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 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)) {
      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);
  };
  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]) {
      if (v->inchord) {
        if (v->chordcount == 1) {
          addfeature(REST, pitch, num*4, denom*(v->default_length));
        };
      pitchline[notes] = pitch_noacc;
        addfeature(NOTE, pitch, num*4, denom*2*(v->default_length));
      } else {
      pitchline[notes] = pitch_noacc;
        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;
      addfeature(NOTE, pitch, num*4, denom*(v->default_length));
      if (!v->inchord) {
        marknote();
      }; 
      if ((v->inslur) && (!v->ingrace)) {
        addfeature(SLUR_TIE, 0, 0, 0);
      };
    };
  };
}

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

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

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

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

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

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

  p = s;
  /* remove any leading spaces */
  skipspace(&p);
  /* remove any trailing spaces */
  q = p;
  while ((*q != '\0') && (*q != ' ')) {
    q = q + 1;
  };
  if (*q == ' ') {
    *q = '\0';
  };
  done = 0;
/* 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;
  };
}; /* 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 (done == 0) {
    sprintf(buff, "instruction !%s! ignored", s);
    event_warning(buff);
  };
}

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

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

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

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

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

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

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

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

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

static void dotie(j, xinchord,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;

  /* find note to be tied */
  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)) {
      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)) {
            /* 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 (lastnote == -1) {
            event_error("Bad tie: possibly two ties in a row");
          } else {
            if (pitch[lastnote] == pitch[tienote]) {
              lasttie = place;
              tietodo = 1;
            };
          };
          break;
        case CHORDON:
          inchord = 1;
          break;
        case CHORDOFF:
        case CHORDOFFEX:
          inchord = 0;
          /* subtract time from tied time */
          addfract(&tied_num, &tied_denom, -num[place], denom[place]);
          break;
        case VOICE:
          localvoiceno = pitch[place];
        default:
          break;
      };
      place = place + 1;
    };
    if (tietodo == 1) {
      event_error("Could not find note to be tied");
    };
  };
}

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 nochordfix;
  int voiceno;

  j = 0;
  inchord = 0;
  nochordfix=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:
      if(nochordfix == 0) fix_enclosed_note_lengths(chord_start,j);
      inchord = 0;
      chord_end=j;
      j = j + 1;
      nochordfix=0;
      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 (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;
             /* make sure we don't run fix_enclosed_notes_again */
              nochordfix=1; 
              }
      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) {
        hostnotestart = j;
      };
      j = j + 1;
    };
  } else {
    hostnoteend = hostnotestart;
  };
  if (hostnotestart == -1) {
    event_error("No note found to follow grace notes");
  } else {
    /* count up grace units */
    grace_num = 0;
    grace_denom = 1;
    p = start;
    while (p <= end) {
      if ((feature[p] == NOTE) || (feature[p] == REST)) {
        grace_num = grace_num * denom[p] + grace_denom * num[p];
        grace_denom = grace_denom * denom[p];
        reduce(&grace_num, &grace_denom);
      };
      p = p + 1;
    };
    /* adjust host note or notes */
    p = hostnotestart;
    while (p <= hostnoteend) {
      if ((feature[p] == NOTE) || (feature[p] == REST) || 
          (feature[p] == CHORDOFF)) {
        next_num = num[p];
        next_denom = denom[p];
        num[p] = num[p] * (gfact_denom - gfact_num);
        denom[p] = next_denom * gfact_denom;
        reduce(&num[p], &denom[p]);
      };
      p = p + 1;
    };
    fact_num = next_num * grace_denom * gfact_num;
    fact_denom = next_denom * grace_num * gfact_denom;
    reduce(&fact_num, &fact_denom);
    /* adjust length of grace notes */
    p = start;
    while (p <= end) {
      lenmul(p, fact_num, fact_denom);
      p = p + 1;
    };
  };
}


static void 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) {
        hostnotestart = j;
      };
      j = j + 1;
    };
  } else {
    hostnoteend = hostnotestart;
  };
  if (hostnotestart == -1) {
    event_error("No note found to follow grace notes");
  } else {
    /* count up grace units */
    grace_num = 0;
    grace_denom = 1;
    p = start;
    while (p <= end) {
      if ((feature[p] == NOTE) || (feature[p] == REST)) {
        grace_num = grace_num * denom[p] + grace_denom * num[p];
        grace_denom = grace_denom * denom[p];
        reduce(&grace_num, &grace_denom);
      };
      p = p + 1;
    };

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

/* is the following note long enough */
   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;

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

static void delendrep(j)
int j;
/* remove bogus repeat */
{
  event_error("spurious repeat after second ending");
  switch(feature[j]) {
  case REP_BAR:
    feature[j] = DOUBLE_BAR;
    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 (assume_repeat_warning == -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 (assume_repeat_warning == -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:
    event_warning("replacing |: with double repeat (::)");
    feature[j] = DOUBLE_REP;
    break;
  case BAR_REP:
    event_error("Too many end repeats");
    break;
  case DOUBLE_REP:
    event_error("Too many end repeats");
    break;
  default:
    event_error("Internal error - please report");
    break;
  };
}

static void fixreps()
/* find and correct missing repeats in music */
/* abc2midi places an extra || at the start of the music */
/* This can be converted to |: if necessary. */
{
  int j;
  int rep_point; /* where to assume a repeat starts */
  int expect_repeat;   /* = 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) {
          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) {
           event_error(" found [1 or like before |:");
           placestartrep(rep_point);
           }
       nplays++;
       break;

    case DOUBLE_REP:           /*  ::  */
      if (!expect_repeat) {
          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;
  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;
  retain_accidentals = 1;
  if (ratio_standard == -1) {
    ratio_a = 2;
    ratio_b = 4;
  } else {
     ratio_a = 2;
     ratio_b = 6;
   }
  wcount = 0;
  parts = -1;
  middle_c = 60;
  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, minor, modmap, modmul, gotkey, gotclef, clefname,
          octave, transpose, gotoctave, gottranspose)
/* handles a K: field */
int sharps; /* sharps is number of sharps in key signature */
int minor; /* a boolean 0 or 1 */
char *s; /* original string following K: */
char modmap[7]; /* array of accidentals to be applied */
int  modmul[7]; /* array giving multiplicity of each accent (1 or 2) */
int gotkey, gotclef;
int octave, transpose, gotoctave, gottranspose;
char* clefname;
{
  if ((dotune) && gotkey) {
    if (pastheader) {
      setmap(sharps, v->basemap, v->basemul);
      altermap(v, modmap, modmul);
      copymap(v);
      addfeature(KEY, sharps, 0, minor);
      if (gottranspose) {
        addfeature(TRANSPOSE, transpose, 0, 0);
      };
    } else {
      if (gottranspose) {
        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();
  


  clearvoicecontexts();
  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");
      };
      printf("writing MIDI file %s\n", outname);
      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];

  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;
     
      v = newvoice(1);
      head = v;

    pastheader = 0;
    if (userfilename == 0) {
      if (outname != NULL) {
        free(outname);
      };
      sprintf(numstr, "%d", n);
      if (strlen(numstr) > namelimit - 1) {
        numstr[namelimit - 1] = '\0';
      };
      if (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(num);
  free(denom);
  free(feature);
  free(words);
  free(outname);
  free(outbase);
}


Generated by  Doxygen 1.6.0   Back to index