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


Table of Contents

struct voicecontext
struct notestruct

struct voicecontext* newvoice()
struct voicecontext* getvoicecontext()
void meter_voice_update()
void dump_voicecontexts()
void dump_trackdescriptor()
void setup_trackstructure()
void clearvoicecontexts()
int getchordnumbers()
void addchordname()
void setup_chordnames()
void event_init()
void event_text()
void event_x_reserved()
void event_abbreviation()
void event_acciaccatura()
int locate_voice()
void sync_voice()
int search_backwards_for_last_bar_line ()
void event_start_extended_overlay()
void event_stop_extended_overlay()
void event_split_voice()
void recurse_back_to_original_voice()
void recurse_back_and_change_bar()
void complete_all_split_voices()
void event_tex()
void event_fatal_error()
void event_error()
void event_warning()
int autoextend()
int textextend()
void addfeature()
void replacefeature()
void insertfeature()
void removefeature()
void removefeatures()
void event_linebreak()
void event_startmusicline()
void event_endmusicline()
void textfeature()
void event_comment()
void parse_mididefs()
char * expand_midix()
void event_specific()
void event_specific_in_header()
void event_startinline()
void event_closeinline()
void extract_filename()
void extract_field()
void event_words()
void append_words()
void appendfield()
void checkbreak()
void char_out()
void read_spec()
void event_part()
void event_voice()
void event_length()
void event_default_length()
void tempounits()
int get_tempo_from_name()
void event_tempo()
void event_timesig()
void event_octave()
void event_info_key()
void stack_broken()
void restore_broken()
void event_graceon()
void event_graceoff()
void event_playonrep()
void event_sluron()
void event_sluroff()
void event_tie()
void event_space()
void event_lineend()
void event_broken()
void event_tuple()
void event_chord()
void lenmul()
void brokenadjust()
void marknotestart()
void marknoteend()
void marknote()
void event_rest()
void event_mrest()
void event_chordon()
void event_chordoff()
void event_finger()
int pitchof()
int barepitch()
int pitchof_b()
int p48tc53()
void convert_to_comma53()
void doroll()
void doroll_setup()
void doroll_output()
void dotrill()
void dotrill_setup()
void dotrill_output()
void dump_notestruct()
void free_notestructs()
void makecut()
void makeharproll()
void makeharproll3()
void doornament()
void hornp()
void event_note()
void event_microtone()
void event_temperament()
void event_normal_tone()
void event_handle_gchord()
void event_handle_instruction()
void setmap()
void altermap()
void copymap()
int mputc()
void addfract()
void nondestructive_readstr()
void dotie()
void fix_enclosed_note_lengths()
int patchup_chordtie()
void tiefix()
void applygrace()
void applygrace_orig()
void applygrace_new()
void dograce()
void zerobar()
void event_bar()
void placeendrep()
void placestartrep()
void fixreps()
void beat_modifier()
void apply_bf_stress_factors()
void startfile()
void headerprocess()
void event_key()
void scan_for_missing_repeats()
void add_missing_repeats()
void convert_tnote_to_note()
void expand_ornaments()
void fix_part_start()
void finishfile()
void event_blankline()
void event_refno()
void event_eof()
int main()



*/

#define VERSION "5.02 February 16 2025 abc2midi" 

/* enables reading V: indication in header */
#define XTEN1 1

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


#ifdef _MSC_VER
#define snprintf _snprintf
#endif

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

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

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

#ifdef ANSILIBS
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#else
extern char* strchr();
extern void reduce();
#endif
/*int snprintf(char *str, size_t size, const char *format, ...);*/
int load_stress_parameters(char *);

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

/* global variables grouped roughly by function */

FILE *fp;
FILE *diaghandle; /* [SS] 2019-03-20 */
int done_with_barloc = 0; /* [SS] 2019-03-21 */

/*#define MAKAM*/
#ifdef MAKAM
FILE *fc53; /* for debugging */
#endif

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

/* parsing stage */
int tuplecount, tfact_num, tfact_denom, tnote_num, tnote_denom;
int specialtuple;
int gracenotes;
int headerpartlabel;
int dotune, pastheader;
int hornpipe, last_num, last_denom;
int timesigset;
int ratio_a, ratio_b;
int velocitychange = 15;
int chordstart=0;
int propagate_accidentals = 2; /* [SS] 2015-08-18 */
/* microtonal support and scale temperament */
int active_pitchbend;
extern struct fraction setmicrotone; /* [SS] 2014-01-07 */
extern int microtone;
extern int temperament;  /* [SS] 2020-06-25 */
/* [HL] 2020-07-03 */
float temperament_dt[12]={0.0,0.0,0.0,0.0, 0.0,0.0,0.0,0.0, 0.0,0.0,0.0,0.0};
#define TEMPERDT -1
#define TEMPEREQ 1
#define TEMPERLN 2
#define TEMPERNORMAL 0
float octave_size = 12.0; /* [SS] 2020-07-17 */
float fifth_size = 7.0; /* default to 12-edo */
float sharp_size = 1.0; /* [SS] 2020-07-17 */
float microstep_size = 1.0; /* [SS] 2020-07-17 */
int started_parsing=0;
int v1index= -1;
int ignore_fermata = 0; /* [SS] 2010-01-06 */
int ignore_gracenotes = 0; /* [SS] 2010-01-08 */
int ignore_guitarchords = 0; /* [SS] 2019-12-09 */
int separate_tracks_for_words = 0; /* [SS] 2010-02-02 */
int bodystarted =0;
int harpmode=0;  /* [JS] 2011-04-29 */
int easyabcmode = 1; /* [SS] 2011-07-18 */
int barflymode = 1; /* [SS] 2011-08-19 */
char rhythmdesignator[32]; /* [SS] 2011-08-19 */
int retuning = 0; /* [SS] 2012-04-01 */
int bend = 8192; /* [SS] 2012-04-01 */
int comma53 = 0; /* [SS] 2014-01-12 */
int silent = 0; /* [SS] 2014-10-16 */
int no_more_free_channels; /* [SS] 2015-03-23 */
void init_p48toc53 (); /* [SS] 2014-01-12 */ 
void convert_to_comma53 (char acc, int *midipitch, int* midibend);  
void recurse_back_to_original_voice (); /* [SS] 2014-03-26 */
void parse_drummap(char **s); /* [SS] 2017-12-10 */
void dump_barloc(FILE *,int); /* [SS] 2019-09-21 */


struct voicecontext {
/* not to be confused with struct voice defined in struct.h and
   used only by yaps.
*/
  /* maps of accidentals for each stave line */
  char basemap[7], workmap[7][10];
  int  basemul[7], workmul[7][10];
  struct fraction basemic[7],workmic[7][10];
  int  keyset; /* flag to indicate whether key signature set */
  int default_length;
  int active_meter_num; /* [SS] 2012-11-08 */
  int active_meter_denom; /* [SS] 2012-11-08 */
  int voiceno; /* voice number referenced by V: command. To avoid
                  conflicts with split voices, all split voices
                  begin from 32. */
  int indexno; /* voice index number in the feat array. It just
                  increments by one and depends on the order the
                  voices are created -- including split voices.
               */
  int topvoiceno,topindexno; /* links to original voice in the split */
  int hasgchords;
  int haswords;
  int hasdrums;
  int hasdrone;
  int inslur;
  int ingrace;
  int octaveshift;
  int lastbarloc;  /* position of last bar line parsed */
  int tosplitno,fromsplitno; /* links to split voices and source voice*/ 
  int last_resync_point;
  /* chord handling */
  int inchord, chordcount;
  int chord_num, chord_denom;
  /* details of last 2 notes/chords to apply length-modifiers to */
  int laststart, lastend, thisstart, thisend; /* for handling broken rhythms*/
  /* broken rhythm handling */
  int brokentype, brokenmult, brokenpending;
  int broken_stack[7];
  int midichannel; /* [SS] 2015-03-24 */
  struct voicecontext* next;
  int drumchannel;
  int nbars; /* [SS] 2019-03-18 */
};
struct voicecontext global;
struct voicecontext* v;
struct voicecontext* head;
struct voicecontext* vaddr[64]; /* address of all voices (by v->indexno) */
/* vaddr is only a convenience for debugging */


/* [SS] 2012-06-30 */
/* structure for expanding a note into a TRILL, ROLL, or ORNAMENT */
struct notestruct {
  int note;
  int index;
  int notetype;
  int pitch;
  int pitchup;
  int bendup;
  int benddown;
  int pitchdown;
  int default_length;
  };

struct notestruct* noteaddr[1000];
int notesdefined = 1;



struct trackstruct trackdescriptor[40]; /* trackstruct defined in genmidi.h*/
 

int detune_list[12]; /* [SS] 2020-06-30 */
int voicecount;
int numsplits=0;
int splitdepth = 0;

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

/* Named guitar chords */
char chordname[MAXCHORDNAMES][8];
/* int chordnotes[MAXCHORDNAMES][6]; */
int chordnotes[MAXCHORDNAMES][10]; /* [SS] 2012-01-29 */
int chordlen[MAXCHORDNAMES];
int chordsnamed = 0;

/* general purpose storage structure */
int maxnotes;
int *pitch, *num, *denom;
int *bentpitch; /* needed for handling microtones */
featuretype *feature;
int *stressvelocity;  /* [SS] 2011-08-17 for Phil's stress model*/
int *pitchline; /* introduced for handling ties */
int *decotype; /* [SS] 2012-06-29 for handling ROLLS, TRILLS, etc. */
int *charloc; /* [SS] 2014-12-25 for storing character position in abc tune */
int notes;

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

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

/* for get_tempo_from_name  [SS] 2010-12-07 */
char *temponame[19] = {"larghissimo" , "adagissimo", "lentissimo",
   "largo", "adagio", "lento", "larghetto", "adagietto", "andante",
   "andantino", "moderato", "allegretto", "allegro", "vivace",
   "vivo", "presto", "allegrissimo", "vivacissimo", "prestissimo"};
int temporate[19] = {40,            44,             48,
    56,      59,       62,       66,        76,        88,
    96,          104,       112,          120,       168,
   180,     192,      208,            220,          240}; 

char * abcm2psoptions[1] = {"setbarnb"};
int number_of_abcm2ps_options = 1;

/* output file generation */
int userfilename = 0;
char *outname = NULL;
char *outbase = NULL;
int check;
int nofnop; /* for suppressing dynamics (ff, pp etc) */
int nocom;  /* for suppressing comments in MIDI file */
int ntracks;

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

/* generating MIDI output */
int middle_c;
extern int channel_in_use[MAXCHANS + 3]; /* 2015-03-16 formerly channels[] */
extern int additive;
int gfact_num, gfact_denom, gfact_method;  /* for handling grace notes */
int programbase = 0; /* [SS] 2017-06-02 */

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


extern int inchordflag; /* [SS] 2012-03-30 */
/* for reseting decorators_passback in parseabc.c */

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

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

char *csmfilename = NULL;  /* [SS] 2013-04-10 */

/* [SS] 2015-06-01 */
#define MAXMIDICMD 200 
char midicmdname[MAXMIDICMD][32];
char *midicmd[MAXMIDICMD];
int nmidicmd = 0;


void addfract(int *xnum, int *xdenom, int a, int b);
static void zerobar();
static void addfeature(int f,int p,int n,int d);
static void replacefeature(int f, int p, int n, int d, int loc);
void insertfeature(int f, int p, int n, int d, int loc);
static void textfeature(int type, char *s);
extern long writetrack();
void init_drum_map();
static void fix_enclosed_note_lengths(int from, int end);
static int patchup_chordtie(int chordstart,int chordend);
static void copymap(struct voicecontext* v);
void init_stresspat();
void beat_modifier(int);
void readstressfile (char * filename);
int parse_stress_params();
void calculate_stress_parameters();
extern int inbody; /* from parseabc.c [SS] 2009-12-18 */
extern int lineposition; /* from parseabc.c [SS] 2011-07-18 */
extern int beatmodel; /* from genmidi.c [SS] 2011-08-26 */
int stress_pattern_loaded; /* [SS] 2018-04-16 */
int stressmodel;

extern int nullputc();
void dumpfeat (int from, int to); /* defined in genmidi.c */
char * concatenatestring(char * s1,char * s2); /* defined in parseabc.c */
void read_custom_stress_file (char *filename); /* defined in stresspat.c */

 
extern long Mf_numbyteswritten; /* linking with midifile.c [SS] 2019-03-23 */

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

  s = (struct voicecontext*) checkmalloc(sizeof(struct voicecontext));
  voicecount = voicecount + 1;
  s->voiceno = n;
  s->indexno = voicecount;
  s->topvoiceno = n;
  s->topindexno = voicecount;
  s->default_length = global.default_length;
  s->active_meter_num = time_num;     /* [SS] 2012-11-08 */
  s->active_meter_denom = time_denom; /* [SS] 2012-11-08 */
  mtime_num = time_num;     /* [SS] 2012-11-08 */
  mtime_denom = time_denom; /* [SS] 2012-11-08 */
  s->hasgchords = 0;
  s->haswords = 0;
  s->hasdrums = 0;
  s->hasdrone = 0;
  s->inslur = 0;
  s->ingrace = 0;
  s->inchord = 0;
  s->chordcount = 0;
  s->lastbarloc = -1;
  s->laststart = -1;
  s->lastend = -1;
  s->thisstart = -1;
  s->thisend = -1;
  s->brokenpending = -1;
  s->tosplitno = -1;
  s->fromsplitno = -1;  
  s->last_resync_point=0;
  s->next = NULL;
    for (i=0; i<7; i++) {
      s->basemap[i] = global.basemap[i];
      s->basemul[i] = global.basemul[i];
      s->basemic[i].num = global.basemic[i].num; /* [SS] 2014-01-08 */
      s->basemic[i].denom = global.basemic[i].denom;
      for (j=0;j<10;j++) {
        s->workmap[i][j] = global.workmap[i][j];
        s->workmul[i][j] = global.workmul[i][j];
        s->workmic[i][j].num =   global.workmic[i][j].num; /* [SS] 2014-01-26 */
        s->workmic[i][j].denom = global.workmic[i][j].denom;
        };
    }
  s->keyset = global.keyset;
  s->octaveshift = global.octaveshift;
  s->drumchannel = 0;
  s->midichannel = -1; /* [SS] 2015-03-24 */
  if (voicecount < 0 || voicecount >63)
     printf("illegal voicecount = %d\n",voicecount); /* [SS] 2012-11-25 */
  vaddr[voicecount] = s;
  s->nbars = 0; /* [SS] 2019-03-18 */
  return(s);
}

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

  p = head;
  q = NULL;
  while ((p != NULL) && (p->voiceno != n)) {
    q = p;
    p = p->next;
  };
  if (p == NULL) {
    p = newvoice(n);
    if (q != NULL) {
      q->next = p;
    };
  };
  if (head == NULL) {
    head = p;
  };
/* check that key signature mapping is set if global
 * key signature set.                             */
  if (p->keyset == 0 && global.keyset)
    {
    p->keyset = 1;
    for (i=0; i<7; i++) {
      p->basemap[i] = global.basemap[i];
      p->basemul[i] = global.basemul[i];
      for (j=0;j<10;j++) {
        p->workmap[i][j] = global.workmap[i][j];
        p->workmul[i][j] = global.workmul[i][j];
        }
      };
    }
  mtime_num = p->active_meter_num; /* [SS] 2012-11-08 */
  mtime_denom = p->active_meter_denom; /* [SS] 2012-11-08 */
  return(p);
}

/* [SS] 2015-03-11 */
void meter_voice_update (int n,int m)
/* for all defined voices update p->active_meter_num and
   p->active_meter_denom
*/
{
struct voicecontext *p;
p = head;
while (p != NULL) {
   p->active_meter_num = n;
   p->active_meter_denom = m;
   p = p->next;
   }
}


void dump_voicecontexts() {
/* called while debugging */
  struct voicecontext *p;
  struct voicecontext *q;

  p = head;
  printf("dump_voicecontexts()\n");
  while (p != NULL) {
    printf("num %d index %d gchords %d words %d drums %d drone %d tosplit %d fromsplit %d\n",
 p->voiceno,p->indexno,p->hasgchords,p->haswords,p->hasdrums,p->hasdrone,p->tosplitno,p->fromsplitno);
    q = p->next;
    p = q;
  };
}


void dump_trackdescriptor() {
 int i;
 for (i=0;i<ntracks;i++) {
    printf("%d %d %d\n",i,trackdescriptor[i].tracktype,trackdescriptor[i].voicenum);
 }
}


void setup_trackstructure () {
  struct voicecontext *p;
  struct voicecontext *q;


  trackdescriptor[0].tracktype=NOTES;
  trackdescriptor[0].voicenum=1;
  trackdescriptor[0].midichannel = -1;

  p = head;
  ntracks = 1;
  while (p != NULL) {
    if (verbose) {
    printf("num %d index %d bars %d gchords %d words %d drums %d drone %d tosplit %d fromsplit %d \n",
 p->voiceno,p->indexno,p->nbars,p->hasgchords,p->haswords,p->hasdrums,p->hasdrone,p->tosplitno,p->fromsplitno);}
    if (ntracks > 39) event_fatal_error("Too many tracks"); /* [SS] 2015-03-26 */
    trackdescriptor[ntracks].tracktype = NOTES;
    trackdescriptor[ntracks].voicenum = p->indexno;
    trackdescriptor[ntracks].midichannel = p->midichannel;
    if (p->haswords) {
       if (!separate_tracks_for_words) {
       trackdescriptor[ntracks].tracktype = NOTEWORDS;
       trackdescriptor[ntracks].voicenum = p->indexno;
       } else {
       ntracks++;
       trackdescriptor[ntracks].tracktype = WORDS;
       trackdescriptor[ntracks].voicenum = trackdescriptor[ntracks-1].voicenum;
       }
    }
    if (p->hasgchords) {
       ntracks++;
       trackdescriptor[ntracks].tracktype = GCHORDS;
       trackdescriptor[ntracks].voicenum = p->indexno;
       }
    if (p->hasdrums) {
        ntracks++;  
        trackdescriptor[ntracks].tracktype = DRUMS;
        trackdescriptor[ntracks].voicenum = p->indexno;
      };
    if (p->hasdrone) {
        ntracks++;  
        trackdescriptor[ntracks].tracktype = DRONE;
        trackdescriptor[ntracks].voicenum = p->indexno;
      };
    ntracks++;
    q = p->next;
    p = q;
    }

/* does the tune need any gchord, drum, drone or word track */
    if ((voicesused == 0) && (!karaoke) && (gchordvoice == 0) && 
        (drumvoice == 0) && (dronevoice==0)) {
      ntracks = 1;
      } 

/*dump_trackdescriptor();*/
}





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 > 10) {
    event_error("Named chord cannot have more than 10 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_m7b5[4] = {0, 3, 6, 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}; [SS] 2017-10-18 */
  static int list_9[5] = {0, 4, 7, 10, 14};
  /* static int list_m9[5] = {0, 3, 7, 10, 2}; */
  static int list_m9[5] = {0, 3, 7, 10, 14};
  /* static int list_maj9[5] = {0, 4, 7, 11, 2}; */
  static int list_maj9[5] = {0, 4, 7, 11, 14};
  /* static int list_M9[5] = {0, 4, 7, 11, 2}; */
  static int list_M9[5] = {0, 4, 7, 11, 14};
  /* static int list_11[6] = {0, 4, 7, 10, 2, 5}; */
  static int list_11[6] = {0, 4, 7, 10, 14, 17};
  static int list_dim9[5] = {0, 3, 6, 9, 13}; /* [SS] 2016-02-08 */

  static int list_sus[3] = {0, 5, 7}; /* [AT] sus is a synonym of sus4 */
  static int list_sus4[3] = {0, 5, 7}; /* [SS] 2015-07-08 [AT] 2024-12-16*/
  static int list_sus2[3] = {0, 2, 7}; /* [AT] 2024-12-16 */
  static int list_sus9[4] = {0, 2, 5, 9}; /* [AT] 2024-12-16 */

  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("m7b5", 4, list_m7b5);
  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); /* [AT] sus is a synonym of sus4 */
  addchordname("sus4", 3, list_sus4); /* [SS] 2015-07-08 [AT] */
  addchordname("sus2", 3, list_sus2); /* [SS] 2015-07-08 [AT] 2024-12-16 */
  addchordname("sus9", 4, list_sus9);  /* [AT] 2024-12-16  2024-12-22*/

  addchordname("7sus2", 4, list_7sus9); /* [SS] 2015-07-08 */
  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;
  int arg,m,n;
  float afreq,semitone_shift; /* [SS] 2012-04-01 */
  double log10();

  /* look for code checking option */
  if (getarg("-c", argc, argv) != -1) {
    check = 1;
  } else {
    check = 0;
  };
  /* disable repeat checking because abc2midi does its own workaround
   * attempting to fix various repeat errors.
   */
  repcheck = 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 */
  arg = getarg("-v", argc, argv);
  if (arg != -1) {  /* [SS] 2011-08-26 */
    if (argc > arg) {
      n = sscanf(argv[arg],"%d",&m);
      if (n > 0) verbose = m; }
      else verbose = 1; /* arg != -1 but arg == argc */
    } else {  /* arg =  -1 */
    verbose = 0;
  };
  if (getarg("-ver",argc, argv) != -1) {
     printf("%s\n",VERSION); /* [SS] 2019-12-30 */
     exit(0);
  }
/* look for "no forte no piano" option */
  if (getarg("-NFNP", argc, argv) != -1) {
    nofnop = 1;
  } else {
    nofnop = 0;
  }

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

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

  /* [SS] 2019-12-09 */
  if (getarg("-NGUI",argc, argv) != -1) {
     ignore_guitarchords = 1;
  } else {
     ignore_guitarchords = 0;
    }
  
  if (getarg("-NCOM", argc, argv) != -1) {
    nocom = 1;
  } else {
    nocom = 0;
  }

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

  if (getarg("-HARP",argc,argv) != -1) {  /* [JS] 2011-04-29 */
    harpmode = 1;
  } else {
    harpmode = 0;
    }

  if (getarg("-EA",argc,argv) != -1) { /* [SS] 2011-07-18 */
    easyabcmode = 1;
  } else {
    easyabcmode = 0;
  }

  arg = getarg("-BF",argc,argv);
  if (arg != -1)  {  /* [SS] 2011-08-26 */
    if (argc > arg) {
      n = sscanf(argv[arg],"%d",&m);
      if (n > 0) stressmodel = m;
      } else stressmodel = 2;
      barflymode = stressmodel; /* [SS] 2018-04-15 */
    } else {
    barflymode = 0;
    stressmodel = 0;
  }

  arg = getarg("-TT",argc,argv); /* [SS] 2012-04-01 */
  if (arg != -1) {
    n =0;
    if (argc > arg) {
       n = sscanf(argv[arg],"%f",&afreq);
       }
    if (n < 1) {printf("expecting float between 415.30 and 466.16 after -TT\n");
               } else {
               retuning = 1;
               semitone_shift = (float) (12.0 * log10(afreq/440.0f)/log10(2.0f)); /* [SS] 2015-10-08 extra parentheses */
               printf("afreq = %f semitone_shift = %f\n",afreq,semitone_shift);         
               if (semitone_shift >= 1.001) {printf("frequency %f must be less than 466.16\n",afreq);
                   retuning = 0;
                  }
               if (semitone_shift <= -1.015) {printf("frequency %f must be greater than 415.0\n",afreq);
                  retuning = 0;
                  }             
               if (retuning) {bend = (int) (8192.0 * semitone_shift) + 8192;
                              if (bend > 16383) bend=16383;
                              if (bend < 0) bend = 0;
                              printf("bend = %d\n",bend);
                              }
               }
    } 

  if (getarg("-OCC",argc,argv) != -1) oldchordconvention=1;
  if (getarg("-silent",argc,argv) != -1) silent = 1; /* [SS] 2014-10-16 */

  maxnotes = 500;
  /* allocate space for notes */
  pitch = checkmalloc(maxnotes*sizeof(int));
  num = checkmalloc(maxnotes*sizeof(int));
  denom = checkmalloc(maxnotes*sizeof(int));
  stressvelocity = checkmalloc(maxnotes*sizeof(int)); /* [SS] 2011-08-17 */
  bentpitch = checkmalloc(maxnotes*sizeof(int));
  decotype  = checkmalloc(maxnotes*sizeof(int)); /* [SS] 2012-06-29 */
  feature = (featuretype*) checkmalloc(maxnotes*sizeof(featuretype));
  pitchline = checkmalloc(maxnotes*sizeof(int));
  charloc = checkmalloc(maxnotes*sizeof(int)); /* [SS] 2014-12-25 */
  for (j=0; j<maxnotes; j++)           /* [SS] 2012-11-25 */
       bentpitch[j] = decotype[j] = 0; /* [SS] 2012-11-25 */
  for (j=0;j<DECSIZE;j++)  dummydecorator[j] = 0;

  /* and for text */
  atext = (char**) checkmalloc(maxtexts*sizeof(char*));
  words = (char**) checkmalloc(maxwords*sizeof(char*));
  if ((getarg("-h", argc, argv) != -1) || (argc < 2)) {
    printf("abc2midi version %s\n",VERSION);
    printf("Usage : abc2midi <abc file> [reference number] [-c] [-v] ");
    printf("[-o filename]\n");
    printf("        [-t] [-n <value>] [-CS] [-NFNP] [-NCOM] [-NFER] [-NGRA] [-NGUI] [-HARP]\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("        -CS use 2:1 instead of 3:1 for broken rhythms\n"); /* [SS] 2016-01-02 */
    printf("        -quiet suppress some common warnings\n");
    printf("        -silent suppresses most messages\n");
    printf("        -Q default tempo (quarter notes/minute)\n");
    printf("        -NFNP don't process !p! or !f!-like fields\n");
    printf("        -NCOM suppress comments in output MIDI file\n");
    printf("        -NFER ignore all fermata markings\n");
    printf("        -NGRA ignore grace notes\n");
    printf("        -NGUI ignore guitar chord indications\n");
    printf("        -STFW separate tracks for words (lyrics)\n");
    printf("        -HARP ornaments=roll for harpist (same pitch)\n"); /* [JS] 2011-04-29 */
    printf("        -BF Barfly mode: invokes a stress model if possible\n");
    printf("        -OCC old chord convention (eg. +CE+)\n");
    printf("        -TT tune to A =  <frequency>\n");
    printf("        -CSM <filename> load custom stress models from file\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]); [RM] 2010-11-21 
    for (j = 0; j< (int) strlen(outbase); j++) {
      if (outbase[j] == '.') outbase[j] = '\0';
    };
  };
*/
outbase = addstring(argv[1]); /* [RM] 2010-11-21 */
 for (j = (int) strlen(outbase); j>0 ; j--)  {
    if (outbase[j] == '.') {
     outbase[j] = '\0';
     break;
    }
  };
 }

  /* 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");
    };
  }

/* [SS] 2013-04-10 */
  j = getarg("-CSM", argc, argv);
  if (j != -1) {
    if (argc >= j+1) {
      csmfilename = addstring(argv[j]);
      if (*csmfilename == '-') {
        event_error("csmfilename confused with options");
        csmfilename = NULL;
       }
  } else {
     event_error("Filename required after -CSM option");
     } 
 }

  ratio_standard = getarg("-CS", argc, argv); /* [SS] 2016-01-02 */
  quiet  = getarg("-quiet", argc, argv);
  dotune = 0;
  parseroff();
  setup_chordnames();

  /* [SS] 2016-01-02 */
  if (getarg("-RS",argc,argv) != -1) {
      event_warning("use -CS to get Celtic broken rhythm");
      }
  
  if (barflymode) init_stresspat();  /* [SS] 2011-08-18 */
}


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

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

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);
  if (quiet == -1) event_warning(msg); /* [JM] 2018-02-22 */
}

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

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


/* support functions for split voices */

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

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

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

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

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

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

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

    case GRACEON:	/*[SS] 2012-03-08 */
       gracenotes = 1;
       break;

    case GRACEOFF:      /*[SS] 2012-03-08 */
       gracenotes = 0;
       break;

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


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

/* [SS] 2015-03-23 */

int extended_overlay_running = 0;

void event_start_extended_overlay()
{
extended_overlay_running = notes;
}

void event_stop_extended_overlay()
{
extended_overlay_running = 0;
/* [SS] 2015-03-26 */
if (v->fromsplitno != -1 || splitdepth >0) recurse_back_to_original_voice();
}


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

int sync_to;

void event_split_voice()
{
int splitno;
int voiceno,indexno;
int topvoiceno,topindexno;
int program;
int octaveshift;
int default_length; /* [SS] 2010-08-28 */
int abasemap[7],abasemul[7]; /* active basemap  [SS] 2013-10-30*/
int midichannel; /* [SS] 2015-03-24 */
int i;
if (!voicesused) insertfeature(VOICE,1,0,0,v1index+1); /* [SS] 2009-12-21 */
voicesused = 1; /* multivoice file */
splitno = v->tosplitno;
program = 0;

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

voiceno = v->voiceno;
indexno = v->indexno;
topvoiceno = v->topvoiceno;
topindexno = v->topindexno;
octaveshift = v->octaveshift;
default_length = v->default_length; 
midichannel = v->midichannel; /* [SS] 2015-03-24 */
if (topvoiceno == voiceno) sync_to = search_backwards_for_last_bar_line(notes-1);
addfeature(SINGLE_BAR,0,0,0);


if (splitno == -1) {splitno = 32+numsplits++;
                   v->tosplitno = splitno;
                   }
/* save basemap and basemul in case it was just changed. We
   need to send it to the split voice. [SS] 2013-10-30
*/
for (i=0;i<7;i++) {
     abasemap[i] = v->basemap[i];
     abasemul[i] = v->basemul[i];
     }

v = getvoicecontext(splitno);
/* propagate the active basemap to the split voice */
for (i=0;i<7;i++) {
     v->basemap[i] = abasemap[i];
     v->basemul[i] = abasemul[i];
     }
copymap(v);
/* end of [SS] 2013-10-30 patch */

/* [SS] 2015-06-30 */
/* need to propogate unit length */
v->default_length = default_length;

splitdepth++;
addfeature(VOICE, v->indexno, 0, 0);
if (v->fromsplitno == -1) {
  v->fromsplitno = voiceno;
  v->topvoiceno = topvoiceno;
  v->topindexno = topindexno;
  v->octaveshift = octaveshift;
  v->default_length = default_length; /* [SS] 2010-08-28 */
 }
/* when syncing the split voice we want to be sure that
   we do not include the notes in the last bar in the source
   voice the notes in the split voice take their place.
*/
v->midichannel = midichannel; /* [SS] 2014-03-24 */
if (extended_overlay_running) 
   sync_voice(v,extended_overlay_running,1);
else
   sync_voice (v,sync_to,1);
}




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

/* This function is not used any more [SS] 2013-12-02 */
void recurse_back_and_change_bar (int type)
{
int previous_voice;
previous_voice = v->fromsplitno;
while (previous_voice >-1 && splitdepth > 0) {
  v = getvoicecontext(previous_voice);
/* [SS] 2013-12-1 */
/*  if (v->lastbarloc > -1) replacefeature(type, 0,0,0, v->lastbarloc);*/
  previous_voice = v->fromsplitno;
  splitdepth--;
  }
addfeature(VOICE, v->indexno, 0, 0);
copymap(v);
/* [SS] 2013-12-1 */
if (v->lastbarloc > -1) replacefeature(type, 0,0,0, v->lastbarloc);
}


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

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

/* end of code for split voices */



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

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

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

  if (nullpass != 1) {
    printf("Error in line-char %d-%d : %s\n", lineno,lineposition, s);
  };
#else
  printf("Error in line-char %d-%d : %s\n", lineno, lineposition, 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-char %d-%d : %s\n", lineno,lineposition, s);
  };
#else
  printf("Warning in line-char %d-%d : %s\n", lineno, lineposition, s);
#endif
}

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

  if (verbose > 2) {
    event_warning("Extending note capacity");
  };
  newlimit = maxnotes*2;
  fptr = (featuretype*) checkmalloc(newlimit*sizeof(featuretype));
  for(i=0;i<maxnotes;i++){
    fptr[i] = feature[i];
  };
  free(feature);
  feature = fptr;
  ptr = checkmalloc(newlimit*sizeof(int));
  ptr2 = checkmalloc(newlimit*sizeof(int));
  ptr3 = checkmalloc(newlimit*sizeof(int));
  ptr4 = checkmalloc(newlimit*sizeof(int)); /* [SS] 2012-06-29 */
  ptr5 = checkmalloc(newlimit*sizeof(int)); /* [SS] 2014-12-25 */
  for (i=0; i<newlimit; i++) {
      ptr3[i] = 0;
      ptr4[i] =0;
      }      
  for(i=0;i<maxnotes;i++){
    ptr[i] = pitch[i];
    ptr2[i] = pitchline[i];
    ptr3[i] = bentpitch[i];
    ptr4[i] = decotype[i];
    ptr5[i] = charloc[i]; /* [SS] 2014-12-25 */
  };
  free(pitch);
  free(pitchline);
  free(bentpitch);
  free(decotype);
  free(charloc); /* [SS] 2014-12-25 */
  pitch = ptr;
  pitchline = ptr2;
  bentpitch = ptr3;
  decotype = ptr4; /* [SS] 2012-06-29 */
  charloc = ptr5; /* [SS] 2014-12-25 */
  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;

/* [SS] 2011-08-17 */
  ptr = checkmalloc(newlimit*sizeof(int));
  for(i=0;i<maxnotes;i++){
    ptr[i] = stressvelocity[i];
  };
  free(stressvelocity);
  stressvelocity = 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 > 2) {
    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;
  charloc[notes] = lineposition; /* [SS] 2014-12-25 */
  if ((f == NOTE) || (f == REST) || (f == CHORDOFF)) {
    reduce(&num[notes], &denom[notes]);
  };
  notes = notes + 1;
  if (notes >= maxnotes) {
    maxnotes = autoextend(maxnotes);
  };
}

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


void insertfeature(f, p, n, d, loc)
/* insert feature in internal table */
int f,p,n,d,loc;
{ int i;
  notes = notes + 1;
  if (notes >= maxnotes) {
    maxnotes = autoextend(maxnotes);
  };
  for (i=notes;i>loc;i--) {
    feature[i] = feature[i-1];
    pitch[i] = pitch[i-1];
    pitchline[i] = pitchline[i-1];
    bentpitch[i] = bentpitch[i-1];
    decotype[i] = decotype[i-1]; /* [SS] 2012-06-29 */
    charloc[i] = charloc[i-1]; /* [SS] 2014-12-25 */
    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;
  charloc[i] = lineposition; /* [SS] 2014-12-25 */
  bentpitch[i] = 0;
  decotype[i] = 0;
}

static void removefeature(loc)
int loc;
{
  int i;
  for (i=loc;i<notes;i++)
    {
    feature[i] = feature[i+1];
    pitch[i]   = pitch[i+1];
    num[i]     = num[i+1];
    denom[i]   = denom[i+1];
    pitchline[i] = pitchline[i+1];
    bentpitch[i] = bentpitch[i+1];
    decotype[i] = decotype[i+1]; /* [SS] 2012-06-29 */
    charloc[i] = charloc[i+1]; /* [SS] 2014-12-25 */

    }
  notes--;
}


static void interchange_features(loc1,loc2)
/* The function switches the contents of the features in loc1 and loc2 */
/* [SS] 2019-01-01                                                     */
int loc1,loc2;
{
int tfeat,tpitch,tnum,tdenom,tpitchline,tbentpitch,tdecotype,tcharloc;
tfeat = feature[loc2];
tpitch = pitch[loc2];
tnum = num[loc2];
tdenom = denom[loc2];
tpitchline = pitchline[loc2];
tbentpitch = bentpitch[loc2];
tdecotype = decotype[loc2];
tcharloc = charloc[loc2];
feature[loc2] = feature[loc1];
pitch[loc2] = pitch[loc1];
num[loc2] = num[loc1];
denom[loc2] = denom[loc1];
pitchline[loc2] = pitchline[loc1];
bentpitch[loc2] = bentpitch[loc1];
decotype[loc2] = decotype[loc1];
charloc[loc2] = charloc[loc1];
feature[loc1] = tfeat;
pitch[loc1] = tpitch;
num[loc1] = tnum;
denom[loc1] = tdenom;
pitchline[loc1] = tpitchline;
bentpitch[loc1] = tbentpitch;
decotype[loc1] = tdecotype;
charloc[loc1] = tcharloc;
}

static void removefeatures(locfrom,locto)
int locfrom,locto;
{
  int i;
  int offset;
  offset = locto - locfrom + 1;
  for (i=locfrom;i<notes;i++)
    {
    feature[i] = feature[i+offset];
    pitch[i]   = pitch[i+offset];
    num[i]     = num[i+offset];
    denom[i]   = denom[i+offset];
    pitchline[i] = pitchline[i+offset];
    bentpitch[i] = bentpitch[i+offset];
    decotype[i] = decotype[i+offset]; /* [SS] 2014-11-09 */
    charloc[i]  = charloc[i+offset]; /* [SS] 2014-12-25 */
    }
  notes -= offset;
}

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

/* a score linebreak character has been encountered */
void event_score_linebreak(char ch)
{
}

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

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

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

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

int readaln(); /* links to parseabc.c */

/* [SS] 2015-06-01 For interpreting the %%MIDIdef command */
void parse_mididef(s)
char *s;
{
char *p;
int i;
if (nmidicmd >= MAXMIDICMD) {
    event_error("Too many %%MIDIdef's ");
    return;
    }
p = s;
skipspace(&p);
i = readaln(midicmdname[nmidicmd], &p, 31);
skipspace(&p);
midicmd[nmidicmd] =  addstring(p);
nmidicmd++;
}


/* [SS] 2015-06-01 For converting the %%MIDIx command to a
   %%MIDI command
*/
void event_midi(char* s);  /* [SS] 2022-03-19 */


void process_midix(s)
char *s;
{
/* The function handles the %%MIDIx command translating all the
   codewords into %%MIDI commands and sending the commands to
   event_midi(). If it encounters two %%MIDI controlstring commands,
   then it also sends a %%MIDI controlcombo so that both
   controlstrings are handled by the !shape! command.
*/
char msg[200];
char *p;
char name[32];
int i,j,k;
p = s;
j = 1;
k = 0;
while (j > 0) {
    skipspace(&p);
    j = readaln(&name,&p,31);
    if (j < 1) break;
    for (i=0; i<nmidicmd;i++) {
	  if (strcmp(midicmdname[i],name) == 0) break;
	  }
/* [SS] 2015-07-15 */
    if (i == nmidicmd) {
        sprintf(msg, "cannot match %%%%MIDIx %s with corresponding MIDIdef", name);
        event_error(msg);
        return; /* [SS] 2024.12.06 */
        }
    if (k > 0 && strncmp(midicmd[i],"controlstring",12) == 0)
        event_midi("controlcombo");
    event_midi(midicmd[i]); 
    if (strncmp(midicmd[i],"controlstring",12) == 0) k++;
    }
}

/* [SS] 2020-07-17 ; [HL] 2020-07-24 */
float compute_fifth_size (octave_size, ndiv)
float octave_size; /* in cents */
int ndiv;
/* rather than compute the fifth_size in cents directly
 * we subtract the octave from the third harmonic.
 * We do it this way  instead of the proportion 3/2 because the
 * octave also can be tempered. When the octave is tempered, the factor 2
 * in 3/2 might cause problems: mix of pure and tempered octaves!).
 */
{
double x, h3;
int n;
float w;
/* h3 (1901.955) is the 3rd harmonic (fifth+octave) represented in cents */
h3 = 1200.0 * log (3.0)/log(2.0);  /* 1200 * log2(3.0) */
x = h3 - octave_size;           /* fifth reduced by the tempered octave */
n = (int) (0.5 + x * ndiv / octave_size);     /* fifth in integer steps */
w = n * octave_size / ndiv;                   /* fifth quantized according to temperament */
return w; /* in cents */
}


/* [SS] 2015-08-11 */
void event_midi(s)
/* Handles %%MIDI commands. It was originally part of
   event_specific.
*/
char *s; 
    {
    int ch;
    char command[40];
    char *p;
    int done;

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

    if (s[0] == '=') {
     event_warning("Do not use MIDI= in I: command. Just use MIDI");
     done = 0;
     } /*[SS] 2024.04.30 */

    if (verbose > 1) printf("event_midi command: %s\n",command);

    if (strcmp(command, "channel") == 0) {
      skipspace(&p);
      ch = readnump(&p) - 1;
      if (v != NULL) {
        if (ch == 9) v->drumchannel = 1;
        else v->drumchannel = 0;
        }
      /* [SS] 2015-03-23 */
      if (ch <0 || ch >15) {
          event_error("channel not between 1 and 16 ");
          ch = 1;
          }
      addfeature(CHANNEL, ch, 0, 0);
      if (v != NULL) {
          if (v->midichannel == -1) v->midichannel = ch; /* [SS] 2015-03-24 */
          }
      done = 1;
    }

    else if ((strcmp(command,"transpose") == 0) || (strcmp(command,"rtranspose") == 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 (strcmp(command,"transpose") == 0) {
          addfeature(GTRANSPOSE, val, 0, 0);
        } else {
          addfeature(RTRANSPOSE, val, 0, 0);
        };
      done = 1;
    }

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

      skipspace(&p);
      val = readnump(&p);
      middle_c = val;
      done = 1;
    }

    /* [SS] 2017-06-02 */
    else if (strcmp(command,"programbase") == 0) {
      int val;
      skipspace(&p);
      val = readnump(&p);
      if (val != 0) val=1;
      programbase = val;
      done = 1;
      }


    else if (strcmp(command, "nobarlines") == 0) {
     propagate_accidentals = 0; /* [SS] 2015-08-18 */
      done = 1;
    }

    else if (strcmp(command, "barlines") == 0) {
      propagate_accidentals = 2; /* [SS] 2015-08-18 */
      done = 1;
    }

    else if (strcmp(command, "fermatafixed") == 0) {
      fermata_fixed = 1;
      done = 1;
    }

    else if (strcmp(command, "fermataproportional") == 0) {
      fermata_fixed = 0;
      done = 1;
    }

    /* [SS] 2014-01-12 */
    else if (strcmp(command, "tuningsystem") == 0) {
      skipspace(&p);
      if (strcmp(p,"comma53") == 0) {
        printf("%s\n",p);
        comma53 = 1;
        init_p48toc53();
        done = 1;
#ifdef MAKAM
        fc53 = fopen("abcmid.txt","w");
#endif
        }
    }

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

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

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


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

    /* [SS] 2015-06-16 */
    else if (strcmp(command, "expand") == 0) {
      int a, b;
      skipspace(&p);
      a = readnump(&p);
      if (*p != '/') {
        event_error("Need / in MIDI expand command (eg trim 1/4)");
      } else {
        p = p + 1;
        b = readnump(&p);
       if (v != NULL) {
        addfeature(EXPAND, 1, 4*a, b*v->default_length);
        } else {
        if (global.default_length == -1) 
          event_error("Need to define L: before expand command: expand command ignored.");
        else 
          addfeature(EXPAND,1,4*a, b*global.default_length);
       };
      };
      done = 1;
    }


    else if (strcmp(command, "gchordon") == 0) {
      addfeature(GCHORDON, 0, 0, 0);
      done = 1;
    }

    else if (strcmp(command, "gchordoff") == 0) {
      addfeature(GCHORDOFF, 0, 0, 0);
      done = 1;
    }

    else if (strcmp(command, "chordname") == 0) {
      char name[20];
      int i, notes[10]; /* [SS] 2012-01-29 */

      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<=10) && (*p == ' ')) { /* [SS] 2012-01-29 */
          skipspace(&p);
          notes[i] = readsnump(&p); 
          i = i + 1;
        };
        addchordname(name, i, notes);
      };
      done = 1;
    }


  else if (strcmp(command, "temperamentlinear") == 0) {
      double octave_cents=0.0;
      double fifth_cents=0.0;
      temperament = TEMPERLN;
      middle_c = 60;

      if (sscanf(p," %lf %lf ",&octave_cents,&fifth_cents) == 2) {
        octave_size = octave_cents;
        fifth_size = fifth_cents;
	sharp_size = 7*fifth_size - 4*octave_size; /* [HL] 2015-05-15] */
        microstep_size = sharp_size;   /* [HL] 2020-06-20 */ 	
	if(verbose) { /* [HL] 2015-05-15] */
	    printf("temperamentlinear:\n"
		   "\targs: %lf %lf\n",octave_cents, fifth_cents);
	    printf("\toctave_size = %.3f (%.3lf cents)\n"
		   "\tfifth_size = %.3f (%.3lf cents)\n"
		   "\twhole-tone size = %.3f (%.3lf cents)\n"
		   "\taccidental_size = %.3f (%.3lf cents)\n"
		   ,
		   octave_size,
		   octave_size,
		   fifth_size,fifth_size,
		   2*fifth_size - octave_size, 2.0*fifth_size-octave_size,
		   sharp_size,sharp_size
		   );
	}

      }
      else {
        event_error("Bad format for temperamentlinear command");
      }
      done = 1;
    }

  /* [HL] 2015-05-15 */
  else if (strcmp(command, "temperamentequal") == 0) {
      double octave_cents;
      float acc_size = -1.0;  /* [SDG] 2020-06-03 */
      int narg, ndiv, fifth_index, sharp_steps;
      narg = sscanf(p," %d %lf %d %d ",&ndiv, &octave_cents, &fifth_index, &sharp_steps);
      switch (narg) {
      case 1:
	  octave_size = 1200.0; /* [SS] 2020-07-17 */
          fifth_size = compute_fifth_size (octave_size, ndiv);
	  acc_size = 7*fifth_size - 4*octave_size;
	  break;
      case 2:
	  octave_size = octave_cents;
          fifth_size = compute_fifth_size (octave_size, ndiv);
	  acc_size = 7*fifth_size - 4*octave_size;
	  break;
      case 3:
	  octave_size = octave_cents;
	  if (fifth_index > 0.0) /* user-defined fifth size */
	      fifth_size =  1.0 * fifth_index * octave_size/ndiv ;
	  else {               /* automatically computed fifth size */
              fifth_size = compute_fifth_size (octave_size, ndiv);
	      }
	  acc_size = 7*fifth_size - 4*octave_size;
	  break;
      case 4:
	  octave_size = octave_cents;
	  if (fifth_index > 0.0) /* user-defined fifth size */
	      fifth_size =  1.0 * fifth_index * octave_size/ndiv ;
	  else {               /* automatically computed fifth size */
              fifth_size = compute_fifth_size (octave_size, ndiv);
	  }
	  /* user-defined accidental size */
	  acc_size = sharp_steps * octave_size/ndiv ;
	  break;
      default:
	  event_error("Bad format for temperamentequal command");
      }
      microstep_size =  octave_size/ndiv; /* [SS] 2020-07-17 */
 
      sharp_size = acc_size;

      if(verbose) {
	  printf("temperamentequal:\n\targs (%d):",narg);
	  if(narg>=1) printf(" %d",ndiv);
	  if(narg>=2) printf(" %.3lf",octave_cents);
	  if(narg>=3) printf(" %d",fifth_index);
	  if(narg>=4) printf(" %d",sharp_steps);
	  printf("\n");

	  printf("\tndiv = %d\n"
		 "\toctave_size = %.3lf cents\n"
		 "\tfifth_size = %.3lf (%d steps)\n"
		 "\twhole-tone size = %.3lf (%d steps)\n"
		 "\taccidental_size = %.3lf (%d steps)\n"
		 ,
		 ndiv,
		 octave_size,
		 fifth_size,
		 (int) ((fifth_size/octave_size*ndiv) + 0.5),
		 2.0*fifth_size-octave_size,
		 (int) (((2*fifth_size-octave_size)*ndiv/octave_size) + 0.5),
		 acc_size,
		 (int) ((acc_size*ndiv/octave_size) + 0.5)
		 );
	         printf("\tmicrostep_size = %.3lf cents (1 step)\n",microstep_size); /* [HL] 2020-06-20*/

      }
      
      if (acc_size < 0)
	  event_warning( "temperamentequal <ndiv> [octave_in_cents] [fifth_in_steps] [sharp_in_steps]\n"
			 "\tValue(s) cause(s) inconsistent (inverted) accidentals.\n"
			 "\tTry with different parameters as, for example,\n"
			 "\tsome multiple of <ndiv> for 'temperamentequal',\n"
			 "\tor force a suitable value for 'sharp_in_steps'.\n"
			 );
      

      temperament = TEMPEREQ;
      middle_c = 60;
      done = 1;
    }

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


  else if (strcmp(command,"drumon") == 0 && dotune) {  /* [SS] 2010-05-26 */
      if (v == NULL) event_fatal_error("%%MIDI drumon must occur after the first K: header");
      addfeature(DRUMON, 0, 0, 0);
      v->hasdrums = 1;
      drumvoice = v->indexno; /* [SS] 2010-02-09 */
      done = 1;
    }
    if (strcmp(command,"drumoff") == 0) {
       addfeature(DRUMOFF, 0, 0, 0);
       done = 1;
    }

  else if (strcmp(command,"droneon") == 0 && dotune) {
      addfeature(DRONEON, 0, 0, 0);
      v->hasdrone = 1;
      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;
           }
    }

  else if (strcmp(command,"droneoff") == 0) {
       addfeature(DRONEOFF, 0, 0, 0);
       done = 1;
    }

  else if (strcmp(command, "deltaloudness") == 0) {
      skipspace(&p);
      velocitychange = readnump(&p);
      done = 1;
      }
	if (strcmp(command, "harpmode") == 0) {  /* [JS] 2011-04-29 */
      skipspace(&p);
      harpmode = readnump(&p);
      done = 1;
    }

/* [SS] 2018-04-16 originally in dodeferred */
  else if (strcmp(command,"ptstress") == 0) {  /* [SS] 2011-07-04 */
     char inputfile[256]; /* [SS] 2011-07-04 */
     skipspace(&p);
     strncpy(inputfile,p,250);
     if (verbose) printf("ptstress file = %s\n",inputfile);
     if (parse_stress_params (inputfile) == -1) readstressfile (inputfile);
     calculate_stress_parameters(); 
     done = 1;
     beatmodel = 2;
     stress_pattern_loaded = 1; /* [SS] 2018-04-16 */
     if (stressmodel && beatmodel != stressmodel) beatmodel=stressmodel;
    }


  if (done == 0) {
      /* add as a command to be interpreted later */
      textfeature(DYNAMIC, s);
    };
  }

/* package-specific command found i.e. %%NAME */
/* [JA] 2024.04.30 introducing in_I */
void event_specific(char *package, char *s, int in_I)
{
  char msg[200], command[40];
  char *p;
  int done;

  if (started_parsing == 0) {
	event_specific_in_header(package,s);
	return;
	}

/* [SS] 2015-06-01 */
  if (strcmp(package,"MIDIdef") == 0) {
     parse_mididef(s);
     return;
     }

  if (strcmp(package,"MIDIx") == 0) {
     process_midix(s);
     }

  if (strcmp(package, "MIDI") == 0) 
     event_midi(s); else {
     
/* Parse %%abc directive */
      done = 0;
      if (strcmp(package, "abc") == 0)
      {
         p = s;
         skipspace(&p);
/* skip '-' character after abc */          
         p++;
         readstr(command, &p, 40);
/* Parse %%abc-copyright */
         if (strcmp(command, "copyright") == 0)
         {            
            int lnth;
            int n;
            char *ptr;

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

           /* Changed by Anselm Lingnau, 2005-01-04, for security */
#ifdef NO_SNPRINTF
          if (sprintf(msg, "%%%s%s", package, s) > sizeof(msg)) {
              event_warning("event_specific: comment too long");
          }
#else
          if (snprintf(msg,sizeof(msg), "%%%s%s", package, s) > sizeof(msg)) {
              event_warning("event_specific: comment too long");
          }
#endif
        event_comment(msg);
        }
      }
     if (strcmp(package, "propagate") == 0) {
     /* [SS] 2015-08-18 */
          p = s;
          p = p + 13;
         /*printf("propagate-accidentals encountered\n");*/
         if (strcmp(p,"not") == 0) {
             propagate_accidentals = 0;
          } else if (strcmp(p,"pitch") == 0) {
             propagate_accidentals = 2;
          } else if (strcmp(p,"octave") == 0) {
             propagate_accidentals = 1;
          } else {
              sprintf(msg, "cannot interpret %%%%propagate-accidentals %s\nThe choices are not, octave or pitch" ,p);
              event_error(msg);
              }
         done = 1;
            }

    if (done == 0)  
    {

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

    }
  }
}



/* global variables that can altered by %%MIDI before tune */
int default_middle_c = 60;
int default_retain_accidentals = 2; /* [SS] 2015-08-18 */
int default_fermata_fixed = 0;
int default_ratio_a = 2;
int default_ratio_b = 6;

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

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

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



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

      skipspace(&p);
      val = readnump(&p);
      default_middle_c = val;
      done = 1;
    };
    if (strcmp(command, "nobarlines") == 0) {
      default_retain_accidentals = 0;
      done = 1;
    };
    if (strcmp(command, "barlines") == 0) {
      default_retain_accidentals = 2;
      done = 1;
    };
    if (strcmp(command, "fermatafixed") == 0) {
      default_fermata_fixed = 1;
      done = 1;
    };
    if (strcmp(command, "fermataproportional") == 0) {
      default_fermata_fixed = 0;
      done = 1;
    };
    if (strcmp(command, "harpmode") == 0) {  /* [JS] 2011-04-29 */
      skipspace(&p);
      harpmode = readnump(&p);
      done = 1;
    };
    if (strcmp(command, "ratio") == 0) {
      int a, b;
      skipspace(&p);
      b = readnump(&p);
      skipspace(&p);
      a = readnump(&p);
      if ((a > 0) && (b > 0)) {
        default_ratio_a = a;
        default_ratio_b = b;
        if (default_ratio_a + default_ratio_b % 2 == 1) {
          default_ratio_a = 2 * a;
          default_ratio_b = 2 * b;
        };
      } else {
        event_error("Invalid ratio");
      };
      done = 1;
    };

    if (strcmp(command, "chordname") == 0) {
      char name[20];
      int i, notes[6];
      skipspace(&p);
      i = 0;
      while ((i<19) && (*p != ' ') && (*p != '\0')) {
        name[i] = *p;
        p = p + 1;
        i = i + 1;
      };
      name[i] = '\0';
      if (*p != ' ') {
        event_error("Bad format for chordname command");
      } else {
        i = 0;
        while ((i<=6) && (*p == ' ')) {
          skipspace(&p);
          notes[i] = readsnump(&p); 
          i = i + 1;
        };
        addchordname(name, i, notes);
      };
      done = 1;
    };


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

    if (strcmp(command,"drummap") == 0) { /* [SS] 2017-12-10 */
      skipspace(&p);
      parse_drummap(&p);
      done = 1;
      }


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


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

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

void extract_filename(char *f)
/* work out filename stem from tune title */
/* name length cannot exceed namelimit characters */
{
  char buffer[256];
  int i;
  char *p;

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

void event_field(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;
        /* strncpy(rhythmdesignator,f,32);  [SS] 2011-08-19 */
	snprintf(rhythmdesignator,sizeof(rhythmdesignator),"%s",f); /* [SEG] 2020-06-04 */
        skipspace(&p);
        if (((strncmp(p, "Hornpipe", 8) == 0) ||
            (strncmp(p, "hornpipe", 8) == 0)) &&
             barflymode ==0) {   /* [SS] 2011-08-19 */
          hornpipe = 1;
          ratio_a = 2; /* [SS] 2016-01-02 */
          ratio_b = 4;
        };
      };
      break;
    default:
      {
        char buff[258]; /* [SDG] 2020-06-03 */
        
        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:");
    };
  };
}

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

  karaoke = 1;
  if (v == NULL) {
    event_error("missplaced w: field. w: field ignored");
    return;
    }
  v->haswords = 1;
  wordvoice = v->indexno;
  words[wcount] = addstring(p);
  if ((append == PLUS_FIELD) || (append == W_PLUS_FIELD)) {
    addfeature(WORDEXTEND, 0, 0, 0);
  };
  addfeature(WORDLINE, wcount, 0, 0);
  if (continuation == 0) {
    addfeature(WORDSTOP, 0, 0, 0);
  };
  wcount = wcount + 1;
  if (wcount >= maxwords) {
    maxwords = textextend(maxwords, &words);
  };
}

/* [SS] 2014-08-16 */
void append_words (morewords)
char *morewords;
{
char *p;
p = concatenatestring(words[wcount-1],morewords);
words[wcount-1] = p;
}

/* [SS] 2014-08-16 */
void appendfield (morewords)
char *morewords;
{
append_words (morewords);
}



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, k, spec_length;
  int stackptr;
  char* stack[10];
  char lastch =  ' '; /* [SDG] 2020-06-03 */
  char errmsg[80];

  stackptr = 0;
  spec_length = strlen(spec);
  k = 0;
  in = spec;
  while (*in != 0 && k < spec_length) {  /* [SS] 2018-03-22 */
    k++;
    if (((*in >= 'A') && (*in <= 'Z')) || (*in == '(') || (*in == '.') ||
         (*in == ')') || (*in == '+') || (*in == '-') || (*in == ' ') || 
         ((*in >= '0') && (*in <= '9'))) {
      if (*in == '.' || *in == ' ') {
        in = in + 1;
        continue;  /* [SS] 2018-03-22 */
      };
/*    if (*in == '+') {     no longer supported
*      additive = 1;
*      in = in + 1;
*    };
*    if (*in == '-') {
*      additive = 0;
*      in = in + 1;
*    };
*/
      if (*in == '+' || *in == '-') in = in + 1; /* [SS] 2017-07-11 */

      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");
        };
      };
    } else { /* [SS] 2018-03-22 */
       sprintf(errmsg,"illegal character \'%c\' in part specification.\nThe P: is ignored.", *in);
       if (!silent) event_error(errmsg);
       parts = -1;
       break;
       }
  }; /* end of spec */
  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 && parts == -1) return; /* [SS] 2014-04-10 */
    if (pastheader) {
      if (((int)*p < 'A') || ((int)*p > 'Z')) {
        if (!silent) 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();
      v1index = notes; /* [SS] 2024-02-14 */
      v = getvoicecontext(1);
    } else {
      parts = 0;
      read_spec(p, &part);
      if (parts == 1) {
        /* might be a label not a specificaton */
        headerpartlabel = 1;
      };
    };
  };
}

void event_voice(n, s, vp)
/* handles a V: field in the abc */
int n;
char *s;
struct voice_params *vp;
{
  if (!voicesused && bodystarted) {event_warning("First V: field occurs past body; will combine this body with this voice.");
     bodystarted = 0;
     }
  if (pastheader || XTEN1) {
    voicesused = 1;
    if (pastheader)  checkbreak();


    v = getvoicecontext(n); 
    addfeature(VOICE, v->indexno, 0, 0); 
    
    /*****if (vp->gotclef)
    {
      event_octave(vp->new_clef.octave_offset, 1);
    }*** [SS] 2024-03.02 */

    if (vp->gotoctave) {
      event_octave(vp->octave,1);
    };
    if (vp->gottranspose) {
      addfeature(TRANSPOSE, vp->transpose, 0, 0);
    };
  } else {
    event_warning("V: in header ignored");
  };
}

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

void event_default_length (n)
/* handles a missing L: field in the abc */
     int 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;
    };
  };
}

int get_tempo_from_name (s) /* [SS] 2010-12-07 */
char *s;
{
int i,n;
if (s == NULL) return 0; /* [SS] 2010-12-10 */
for (i=0;i<19;i++) {
   if (strcasecmp(s,temponame[i]) == 0)
      {n = temporate[i];
       return n;
      }
  }
return 0;
}

int is_abcm2ps_option (s) /* [SS] 2018-12-17 */
char *s;
{
int i;
if (s == NULL) return 0; 
for (i=0;i< number_of_abcm2ps_options; i++) {
   if (strcasecmp(s,abcm2psoptions[i]) == 0)
       return 1;
      }
return 0;
}


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) n = get_tempo_from_name(pre); /* [SS] 2010-12-07 */
  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 (timesig)
/* handles an M: field  M:n/m */
  timesig_details_t *timesig;
{
  int dochecking;

  if (timesig->type == TIMESIG_FREE_METER) {
    dochecking = 0;
  } else {
    dochecking = 1;
  }
  if (dotune) {
    if (pastheader) {
      addfeature (TIME, dochecking, timesig->num, timesig->denom);
      mtime_num = timesig->num;            /* [SS] 2012-11-03 */
      mtime_denom = timesig->denom;          /* [SS] 2012-11-03 */
      if (v != NULL) {
        v->active_meter_num = timesig->num; /* [SS] 2012-11-08 */
        v->active_meter_denom = timesig->denom; /* [SS] 2012-11-08 */
      }
    } else {
      time_num = timesig->num;
      time_denom = timesig->denom;
      mtime_num = timesig->num;            /* [SS] 2012-11-03 */
      mtime_denom = timesig->denom;          /* [SS] 2012-11-03 */

      /* [SS] 2015-03-11                                */
      /*if (v != NULL) {                                */
      /*  v->active_meter_num =  n;  [SS] 2012-11-08    */
      /*  v->active_meter_denom =  m;  [SS] 2012-11-08  */
      /*  }                                             */
      meter_voice_update (timesig->num, timesig->denom); /* [SS] 2015-03-11 */
      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;
int local;
{
  if (dotune) {
    if (pastheader || local) {
      v->octaveshift = num;
    } else {
      global.octaveshift = num;
    };
  };
}

/* deleted event_info_key(key,value). It is now in parser2.c [JA] 2024-04-30 */

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



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

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


void event_tie()
/* a tie - has been encountered in the abc */
{
if (gracenotes && ignore_gracenotes) return; /* [SS] 2010-01-12 */
if (feature[notes-1] == CHORDOFF ||
    feature[notes-1] == CHORDOFFEX) { /* did a TIE connect with a chord */
       patchup_chordtie(chordstart,notes-1);
      }
  else
  addfeature(TIE, 0, 0, 0);
}

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

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

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

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

void event_chord()
/* a + or [ or ]  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)
       || (feature[n] == CHORDOFFEX)) /* [SS] 2013-04-20 */ {
    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;
    default:
      num1 = num2 = 1; /* [SDG] 2020-06-03 */
  };
  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. Notes not equal durations");
  } else {
/*
    printf("Adjusting %d to %d and %d to %d\n",
           v->laststart, v->lastend, v->thisstart, v->thisend);
*/
    for (j=v->laststart; j<=v->lastend; j++) {
      lenmul(j, num1, denom12);
    };
    for (j=v->thisstart; j<=v->thisend; j++) {
      lenmul(j, num2, denom12);
    };
  };
}



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

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

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

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

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

  num = n;
  denom = m;
  if (decorators[FERMATA] && !ignore_fermata) {
    if (fermata_fixed) addfract(&num,&denom,1,1);
    else num = num*2;
  };
  if (v == NULL) {
    event_fatal_error("Internal error : no voice allocated");
  };
  if (v->inchord) v->chordcount = v->chordcount + 1;
  if (tuplecount > 0) {
    num = num * tfact_num;
    denom = denom * tfact_denom;
    if (tnote_num == 0) {
      tnote_num = num;
      tnote_denom = denom;
    } else {
      if (tnote_num * denom != num * tnote_denom) {
        if (!specialtuple && quiet == -1) {
          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++) {
    /* [SS] 2012-11-03 */
    /*event_rest(decorators,time_num*(v->default_length), time_denom,0);*/
    event_rest(decorators,mtime_num*(v->default_length), mtime_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 */
/* and is used here to handle fermatas.                          */
{
  inchordflag = 1; /* [SS] 2012-03-30 */
  apply_fermata_to_chord = chorddecorators[FERMATA]; /* [SS] 2012-03-26 */
  if (v->inchord) {
    event_error("Attempt to nest chords");
  } else {
    chordstart = notes;
    if (easyabcmode) 
         addfeature(META,0,lineno,lineposition); /* [SS] 2011-07-18 */
    addfeature(CHORDON, 0, 0, 0);
    v->inchord = 1;
    v->chordcount = 0;
    v->chord_num = 0;
    v->chord_denom = 1;
    marknotestart();
  };
}


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

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

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

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

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

static int pitchof(note, accidental, mult, octave, propagate_accs)
/* This code is used for handling gchords */
/* finds MIDI pitch value for note */
/* if propagate_accs is 1, apply any accidental to all instances of  */
/* that note in the bar. If propagate_accs is 0, accidental does not */
/* apply to other notes */
char note, accidental;
int mult, octave;
int propagate_accs;
{
  int p;
  char acc;
  int mul, noteno;
  static int scale[7] = {0, 2, 4, 5, 7, 9, 11};
  char *anoctave = "cdefgab";

  p = (int) ((long) strchr(anoctave, note) - (long) anoctave);
  p = scale[p];
  acc = accidental;
  mul = mult;
  noteno = (int)note - 'a';
  if (acc == ' ' && !microtone ) { 
/* if microtone do not propagate accidentals to this
   note.
*/
    acc = v->workmap[noteno][octave+4];
    mul = v->workmul[noteno][octave+4];
  } else {
    if (propagate_accs) {
      v->workmap[noteno][octave+4] = acc;
      v->workmul[noteno][octave+4] = mul;
    };
  };
  if (acc == '^') p = p + mul;
  if (acc == '_') p = p - mul;
  return p + 12*octave + middle_c;
}

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

static int pitchof_b(note, accidental, mult, octave, propagate_accs,pitchbend)
/* computes MIDI pitch for note. If global temperament is set,
   it will apply a linear temperament and return a
   pitchbend. If propagate_accs == 2, apply any accidental to all
   instances of  that note of the same pitch irrespective of the
   octave in the bar. If propagate_accs == 1, apply any accidental
   to all notes of the same pitch and same octave in the bar.
   If propagate_accs = 0, do not apply the accidental to other
   notes in the bar.
*/
char note, accidental;
int mult, octave;
int propagate_accs;
int *pitchbend;
{
  int p;
  char acc;
  int mul, noteno;
  float pitchvalue;
  float microtoneshift; /* [SS] 2025-01-03 */
  int pitch,bend;
  int a,b;
  int j;

  static int scale[7] = {0, 2, 4, 5, 7, 9, 11};
  const float accidental_size = sharp_size/100.0;  /* [HL] 2015-05-15 - for temperamentlinear and temperamentequal */
  const float tscale[7] = {
    0,
    (2*fifth_size-octave_size),
    (4*fifth_size-2*octave_size),
    (-1*fifth_size+octave_size),
    fifth_size,
    (3*fifth_size-octave_size),
    (5*fifth_size-2*octave_size) };
  /* contains the pitches of c,d,etc in cent units after accounting
     for fifth_size and octave_size
  */

  static const char *anoctave = "cdefgab";
  acc = accidental;
  mul = mult;
  noteno = (int)note - 'a';


  /* [SS] 2015-08-18 */
  if (acc == ' ' && !microtone) {  /* no accidentals, apply current state */
    acc = v->workmap[noteno][octave+4];
    mul = v->workmul[noteno][octave+4];
    a = v->workmic[noteno][octave+4].num;   /* 2014-01-26 */
    b = v->workmic[noteno][octave+4].denom;
/* [SS} 2025-01-03
   if the key signature contains microtones which applies to the
   note, we call event_microtone which will return the globals
   microtone, setmicrotone.num and setmicrotone.denom and
   they are applied to the note.
*/
    
    if(a != 0) {
       event_microtone(1,a,b); /* [SS] 2025-01-03 */
       }
  } else {  /* some accidentals save the state if propagate_accs != 0 */
    if (propagate_accs) {
      if (propagate_accs == 1) {  /* accidentals applies to only current octave */
        v->workmap[noteno][octave+4] = acc;
        v->workmul[noteno][octave+4] = mul;
        /* [SS] 2014-01-26 */
        v->workmic[noteno][octave+4].num   = setmicrotone.num;
        v->workmic[noteno][octave+4].denom = setmicrotone.denom;
      } else { for (j=0;j<10;j++) { /* accidentals apply to all octaves */
        v->workmap[noteno][j] = acc;
        v->workmul[noteno][j] = mul;
        /* [SS] 2014-01-26 */
        v->workmic[noteno][j].num   = setmicrotone.num;
        v->workmic[noteno][j].denom = setmicrotone.denom;
        }
     }
   }
 };



  p = (int) ((long) strchr(anoctave, note) - (long) anoctave);
  /* [HL] 2020-07-03 */
  if ((temperament==TEMPERLN) || (temperament==TEMPEREQ))  {
 
    pitchvalue = tscale[p]/100.0; /* cents to semitones */

    /* respect accidentals when they do not occur in a microtone */
    if (acc == '^' && !microtone) pitchvalue = pitchvalue + (float) (mul*accidental_size); /* [SS] 2025-02-15 */
    if (acc == '_' && !microtone) pitchvalue = pitchvalue - (float) (mul*accidental_size);
    /* printf("note = %c accidental = %c mul = %d p = %d pitchvalue = %f\n",note,accidental,mul,p,pitchvalue); */

    pitchvalue =  pitchvalue + octave*octave_size/100.0 + middle_c;
    
    /* [HL] 2020-06-27 Adjust for A=440.0 with zero pitchbend */
    pitchvalue += 9.0 - (3.0*fifth_size-octave_size)/100.0;

    /* [HL] 2015-05-15  2025-01-06*/
    if (microtone) {
        /* [SS] 2025-01-03  2025-01-06*/
        if (setmicrotone.denom == 1) 
          /* microtone shortcut (eg _1B,) */
          microtoneshift =  setmicrotone.num * microstep_size/100.0;
        else
          /* microtone fraction (eg _12/53B,) */
          microtoneshift =  (float) setmicrotone.num /(float) setmicrotone.denom;
        /* printf("microtoneshift = %f for setmicrotone %d %d\n",microtoneshift, setmicrotone.num,setmicrotone.denom); */
        pitchvalue += microtoneshift*accidental_size; /* [SS] 2025.02.16 */
       }

	microtone = 0;
	setmicrotone.num = 0; 
        setmicrotone.denom = 1; /* [SS] 2025-01-06 */
	active_pitchbend = 8192;
 
    pitch =  (int) (pitchvalue + 0.5);
    bend = (int) (0.5 + 8192.0 + 4096.0 * (pitchvalue - (float) pitch));
    bend = bend<0?0:(bend>16383?16383:bend);
   } else { /* TEMPERNORMAL / TEMPERDT */
    p = scale[p];
    if (acc == '^' && !microtone) p = p + mul; /* [SS] 2014-01-20 */
    if (acc == '_' && !microtone) p = p - mul;
    pitch = p + 12*octave + middle_c;
    bend = 8192; /* corresponds to zero bend */
    if (temperament == TEMPERDT) {  /* [HL] 2020-07-03 */
      bend += (int) (0.5 + 40.96 * temperament_dt[p]);
	bend = bend<0?0:(bend>16383?16383:bend);
    }
   }
if (!microtone) *pitchbend = bend; /* don't override microtone */
/* if (comma53) [SDG] 2020-06-03 ambiguous statement
 * should the following lines be included in if statement? */
/*#ifdef MAKAM
 if (comma53) fprintf(fc53,"%c%d ",note,octave+4);
#endif
*/
/*printf("note = %c accidental= %c pitch = %d bend = %d\n",note,accidental,pitch,bend);*/
 if (comma53) convert_to_comma53 (acc,  &pitch, pitchbend); 
 microtone = 0; /* [SS] 2014-01-25 */
 setmicrotone.num = 0; /* [SS] 2014-01-25 */
 setmicrotone.denom = 1; /* [SS] 2025-01-06 */
return pitch; 
}



/* [SS] 2014-01-12  comma53 support: start */

int p48toc53[50];

void init_p48toc53 () {
int i,c;
c = 0;
for (i=0; i< 48; i++) {
  p48toc53[i] = c;
/* if black note leave room for extra comma */
  if (i == 4 || i == 12 || i == 24 || i == 32 || i == 40)
     c = c+2;
  else
     c = c+1;
  //printf("%d  ",p48toc53[i]);
  }
}

void convert_to_comma53 (char acc, int *midipitch, int* midibend) 
{
/* The function converts *midipitch, *midibend to the
   closest comma53 pitch values.
*/
float c53factor = 0.22641509f;
float eqtempmidi,bendvalue,c53midi;
int p48,octave,c53;
bendvalue = (float) ((*midibend - 8192.0)/4096.0);
eqtempmidi = (float) (*midipitch) + bendvalue;
octave = (int) (eqtempmidi / 12.0);
p48 = (int) (eqtempmidi * 4.0) % 48;
c53 = p48toc53[p48] + 53*octave;
/* handle b4 or #5 (eg D4b4 or C4#5) in nameAE */
if (p48 == 4 || p48 == 12 || p48 == 24 || p48 == 32 || p48 == 40)
   if (bendvalue > 1.1 || bendvalue < -0.8) c53++;
#ifdef MAKAM
fprintf(fc53,"%d\n",c53);
#endif

/*printf("p48 = %d\n c53 = %d\n",p48,c53);*/
c53midi = (float) c53 * c53factor;
*midipitch = (int) c53midi;
bendvalue = c53midi - (float) *midipitch;
*midibend = 8192 + (int) (bendvalue * 4096);
}


/* [SS] 2014-01-12 */





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

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



/* [SS] 2012-06-30 */
static void doroll_setup(note,octave,n,m,pitch)
char note;
int octave, n, m;
int pitch;
{
  char up, down;
  int t;
  int upoct, downoct, pitchup, pitchdown;
  int bend_up,bend_down;
  char *anoctave = "cdefgab";
  struct notestruct *s;

  upoct = octave;
  downoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  down = *(anoctave + ((t+6) % 7));
  if (up == 'c') upoct = upoct + 1;
  if (down == 'b') downoct = downoct - 1;
  pitchup = pitchof_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend_up);
  pitchdown = pitchof_b(down, v->basemap[(int)down - 'a'], 1, downoct, 0,&bend_down);

  s = (struct notestruct*) checkmalloc(sizeof(struct notestruct));
  s->pitch = pitch;
  s->index = notes;
  s->notetype = ROLL;
  s->pitchup = pitchup;
  s->pitchdown = pitchdown;
  s->bendup = bend_up;
  s->benddown = bend_down;
  s->default_length = global.default_length;
  if (notesdefined < 0 || notesdefined>999)
      printf("illegal notesdefined = %d\n",notesdefined);
  noteaddr[notesdefined] = s;
  if (notesdefined < 1000) notesdefined++;
}

static void doroll_output(i)
int i;
{
int deco_index;
int pitch,pitchup,pitchdown,bend_up,bend_down;
int default_length;
int n,m;
int a,b;
struct notestruct *s;
deco_index = decotype[i];
s =  noteaddr[deco_index];
pitch = s->pitch;
pitchdown = s->pitchdown;
pitchup = s->pitchup;
active_pitchbend = bentpitch[i];
bend_up =  s->bendup;
bend_down =  s->benddown;
default_length = s->default_length;
n = num[i]*default_length;
m = denom[i]*4;
reduce(&n,&m);
a = n*4;
b = m*default_length*5;
reduce(&a,&b);
replacefeature(NOTE, pitch, a, b,i);
i++;
insertfeature(NOTE, pitchup, a, b,i);
bentpitch[i] = bend_up;
i++;
insertfeature(NOTE, pitch, a, b,i);
bentpitch[i] = active_pitchbend;
i++;
insertfeature(NOTE, pitchdown, a, b,i);
bentpitch[i] = bend_down;
i++;
insertfeature(NOTE, pitch, a, b,i);
bentpitch[i] = active_pitchbend;
}


/* dotrill is nolonger used [SDG] 2020-06-03 */
#if 0  
static void dotrill(note, octave, n, m, pitch)
/* applies a trill to a note */
char note;
int octave, n, m;
int pitch;
{
  char up;
  int i, t;
  int upoct, pitchup;
  char *anoctave = "cdefgab";
  int a, b, count;
  int bend;

  upoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  if (up == 'c') upoct = upoct + 1;
  pitchup = pitchof_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend);
  a = 4;
  b = m*(v->default_length);
  count = n;
  while ((tempo*a)/((long)b) > 100000L) {
    count = count*2;
    b = b*2;
  };
  i = 0;
  while (i < count) {
    /*if (i == count - 1) {  **bug** [SS] 2006-09-10 */
     if (i == 0) {
      marknotestart();
    };
    if (i%2 == 0) {
      bentpitch[notes] = bend;
      addfeature(NOTE, pitchup, a, b);
    } else {
      bentpitch[notes] = active_pitchbend;
      addfeature(NOTE, pitch, a, b);
    };
    i = i + 1;
  };
  marknoteend();
}
#endif

static void dotrill_setup(note, octave, n, m, pitch)
char note;
int octave, n, m;
int pitch;
{
  char up;
  int t;
  int upoct, pitchup;
  char *anoctave = "cdefgab";
  int bend;
  struct notestruct *s;

  upoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  if (up == 'c') upoct = upoct + 1;
  pitchup = pitchof_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend);
  s = (struct notestruct*) checkmalloc(sizeof(struct notestruct));
  s->pitch = pitch;
  s->index = notes;
  s->notetype = TRILL;
  s->pitchup = pitchup;
  s->pitchdown = 0;
  s->default_length = global.default_length;
  s->bendup = bend;
  s->benddown = active_pitchbend;
  if (notesdefined < 0 || notesdefined>999)
      printf("illegal notesdefined = %d\n",notesdefined);
  noteaddr[notesdefined] = s;
  if (notesdefined < 1000) notesdefined++;
  }

static void dotrill_output(i)
int i;
{
int deco_index;
int pitch,pitchup,pitchdown;
int bend;
int default_length;
int n,m,a,b;
int count,j;
struct notestruct *s;
deco_index = decotype[i];
s =  noteaddr[deco_index];
pitch = s->pitch;
pitchdown = s->pitchdown;
pitchup = s->pitchup;
default_length = s->default_length;
bend = s->bendup;
active_pitchbend = s->benddown;
n = num[i]*default_length;
m = denom[i]*4;
reduce(&n,&m);
removefeature(i);
  a = 4;
  b = m*default_length;
  count = n;
  while ((tempo*a)/((long)b) > 100000L) {
    count = count*2;
    b = b*2;
  };
  j = 0;
reduce(&a,&b);
  while (j < count) {
    /*if (i == count - 1) {  **bug** [SS] 2006-09-10 */
    if (j%2 == 0) {
      insertfeature(NOTE, pitchup, a, b,i);
      bentpitch[i] = bend;
      i++;
    } else {
      insertfeature(NOTE, pitch, a, b,i);
      bentpitch[i] = active_pitchbend;
      i++;
    };
    j = j + 1;
  };
}


void dump_notestruct () {
  int i;
  struct notestruct *s;
  for (i = 0;i<notesdefined; i++) {
    s = noteaddr[i];
    printf("%d ",i);
    printf("%d ",s->index);
    printf("%d ",s->notetype);
    printf("%d ",s->pitch);
    printf("%d ",s->pitchup);
    printf("%d\n",s->pitchdown);
    }
  } 

/* [SS] 2012-07-03 */

void free_notestructs () {
int i;
struct notestruct *s;
for (i=1;i<notesdefined;i++) {
  s =  noteaddr[i];
  free(s);
  }
}  
  

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

void makeharproll (pitch, bend,n,m)   /* [JS] 2011-04-29 */
int pitch,bend,n,m;
{
bentpitch[notes] = bend;
addfeature(NOTE, pitch, 4*n/2,m*2*v->default_length);
bentpitch[notes] = bend;
addfeature(NOTE, pitch, 4*n/2,m*2*v->default_length);
bentpitch[notes] = bend;
addfeature(NOTE, pitch, 4*n/2,m*v->default_length);
}

void makeharproll3 (pitch, bend,n,m) /* [JS] 2011-04-29 */
int pitch,bend,n,m;
{
int a=n-1;
bentpitch[notes] = bend;
addfeature(NOTE, pitch, 4*(a)/2,m*2*v->default_length);
bentpitch[notes] = bend;
addfeature(NOTE, pitch, 4*(a)/2,m*2*v->default_length);
bentpitch[notes] = bend;
addfeature(NOTE, pitch, 4*(n/2+1),m*v->default_length);
}


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

  upoct = octave;
  downoct = octave;
  t = (int) ((long) strchr(anoctave, note)  - (long) anoctave);
  up = *(anoctave + ((t+1) % 7));
  down = *(anoctave + ((t+6) % 7));
  if (up == 'c') upoct = upoct + 1;
  if (down == 'b') downoct = downoct - 1;
  if (harpmode)	/* [JS] 2011-04-29 */
  {
	  pitchup=pitch;
	  pitchdown=pitch;
	  bend_up=8192;
	  bend_down=8192;
  }
  else
  {
	pitchup = pitchof_b(up, v->basemap[(int)up - 'a'], 1, upoct, 0,&bend_up);
	pitchdown = pitchof_b(down, v->basemap[(int)down - 'a'], 1, downoct, 0,&bend_down);
  }
  marknotestart();
  /* normalize notelength to L:1/8 */
  /*  nnorm =n*8; [SS] 2008-06-14 */
  nnorm = n*(v->default_length);
  mnorm =m*(v->default_length);
  reduce(&nnorm,&mnorm);
  if (nnorm == 3 && mnorm == 1) /* dotted quarter note treated differently */
  {
	 if (harpmode)  /* [JS] 2011-04-29 */
	 {
	   makeharproll3(pitch,active_pitchbend,n,m);
	 }
	 else
	 {
		 nn = n/3; /* in case L:1/16 or smaller */
		 if(nn < 1) nn=1;
		 bentpitch[notes] = active_pitchbend; /* [SS] 2006-11-3 */
		 addfeature(NOTE, pitch, 4*nn,v->default_length);
		 makecut(pitch,pitchup,active_pitchbend,bend_up,nn,m);
		 makecut(pitch,pitchdown,active_pitchbend,bend_down,nn,m);
	 }
  }
  else 
  {
	   if (harpmode) /* [JS] 2011-04-29 */
	   {
		   makeharproll(pitch,active_pitchbend,n,m);
	   }
	   else
	   {
		   makecut(pitch,pitchup,active_pitchbend,bend_up,n,m);
	   }
  }
  marknoteend();
}



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

void event_note(decorators, clef, accidental, mult, note, xoctave, n, m)
/* handles a note in the abc */
int decorators[DECSIZE];
cleftype_t *clef; /* [JA] 2020-10-19 */
int mult;
char accidental, note;
int xoctave, n, m;
{
  int num, denom;
  int octave;
  int pitch;
  int pitch_noacc = 0;
  int dummy;

  decotype[notes] = 0; /* [SS] 2012-07-02 no decoration */
  if (voicesused == 0) bodystarted=1;
  if (v == NULL) {
    event_fatal_error("Internal error - no voice allocated");
  };
  if (gracenotes && ignore_gracenotes) return; /* [SS] 2010-01-08 */

/* [SS] 2024-03-02 2025-02-02 don't apply both clef->octave_offset and
   v->octaveshift
*/
  /*printf("clef->octave_offset = %d v->octaveshift = %d\n",clef->octave_offset,v->octaveshift);*/
  if (v->octaveshift == 0) {   /*[JA] 2021-05-21 */
    octave = xoctave + clef->octave_offset;
  } else {
    octave = xoctave + v->octaveshift;
  }

//  octave = clef->octave_offset + v->octaveshift + xoctave;  /*[SS] 2024-03-02*/
  num = n;
  denom = m;
  if (v->inchord) v->chordcount = v->chordcount + 1;
/*  if (tuplecount > 0 && !v->inchord) { [SS] 2008-06-13} */
  if (tuplecount > 0 ) {
    num = num * tfact_num;
    denom = denom * tfact_denom;
    if (tnote_num == 0) {
      tnote_num = num;
      tnote_denom = denom;
    } else {
      if (tnote_num * denom != num * tnote_denom) {
        if (!specialtuple && quiet == -1) {
          event_warning("Different length notes in tuple");
        };
      };
    };
    if ((!gracenotes) && (!v->inchord)) {
      tuplecount = tuplecount - 1;
    };
  };
  if ((!v->ingrace) && (!v->inchord)) {
    hornp(num, denom*(v->default_length));
  } else {
    last_num = 3; /* hornpiping (>) cannot follow chord or grace notes */
  };
  if ((!v->ingrace) && ((!v->inchord)||(v->chordcount==1))) {
    addunits(num, denom*(v->default_length));
  };

/* linear temperament support */
  if (v->drumchannel) pitch = barepitch(note,accidental,mult,octave);
  /* [SS] 2015-08-18 */
  pitch = pitchof_b(note, accidental, mult, octave, propagate_accidentals,&active_pitchbend);
  if (decorators[FERMATA] && !ignore_fermata) {
    if(fermata_fixed) addfract(&num,&denom,1,1);
    else num = num*2;
  };
  if (v->chordcount == 1) {
    v->chord_num = num*4;
    v->chord_denom = denom*(v->default_length);
  };
  if ((decorators[ROLL]) || (decorators[ORNAMENT]) || (decorators[TRILL])) {
    if (v->inchord) {
      event_error("Rolls and trills not supported in chords");
      pitchline[notes] = pitch_noacc; /* [SS] 2013-03-26 */
      bentpitch[notes] = active_pitchbend; /* [SS] 2013-03-26 */
      addfeature(NOTE, pitch, num*4, denom*2*(v->default_length)); /* [SS] */
    } else {
      if (easyabcmode) /* [SS] 2011-07-18 */ 
         addfeature(META,0,lineno,lineposition); /* [SS] 2011-07-18 */
      if (decorators[TRILL]) {
        decotype[notes] = notesdefined; /* [SS] 2012-06-29 */
        /*dotrill(note, octave, num, denom, pitch);*/
        dotrill_setup(note, octave, num, denom, pitch);
        addfeature(NOTE, pitch, num*4, denom*(v->default_length));
      }
      else if (decorators[ORNAMENT]) {
        doornament(note, octave, num, denom, pitch);
      }
      else { 
        decotype[notes] = notesdefined; /* [SS] 2012-06-29 */
        /*doroll(note, octave, num, denom, pitch);*/
        doroll_setup(note, octave, num, denom, pitch);
        bentpitch[notes] = active_pitchbend;
        addfeature(NOTE, pitch, num*4, denom*(v->default_length));
      };
     marknote(); /* [SS] 2019-10-13 */
     }; /* end of else block for not in chord */
   } /* end of if block for ROLL,ORNAMENT,TRILL */
   else {
    if (decorators[STACCATO] || decorators[BREATH]) {
      if (v->inchord) {
        if (v->chordcount == 1) {
          addfeature(REST, pitch, num*4, denom*(v->default_length));
        };
	pitchline[notes] = pitch_noacc;
        bentpitch[notes] = active_pitchbend;
        addfeature(NOTE, pitch, num*4, denom*2*(v->default_length));
      } else {
	pitchline[notes] = pitch_noacc;
        if (easyabcmode) /* [SS] 2011-07-18 */ 
         addfeature(META,0,lineno,lineposition); /* [SS] 2011-07-18 */
        bentpitch[notes] = active_pitchbend; /* [SS] 2012-05-29 */
        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;
    if (easyabcmode && !v->inchord) /* [SS] 2011-07-18 */ 
         addfeature(META,0,lineno,lineposition); /* [SS] 2011-07-18 */
      bentpitch[notes] = active_pitchbend; /* [SS] 2012-05-29 */
      addfeature(NOTE, pitch, num*4, denom*(v->default_length));
      if (!v->inchord) {
        marknote();
      }; 
      if ((v->inslur) && (!v->ingrace)) {
        addfeature(SLUR_TIE, 0, 0, 0);
      };
    };
  };
      /*printf(": notes = %d v->thisend = %d\n",notes,v->thisend); [SS] 2019-10-13*/
}

/* [SS] 2020-06-30 */
void event_temperament(char* line)
{
char *p = line+13;
int i;
float dt[12];
i = sscanf(p,"%f %f %f %f %f %f %f %f %f %f %f %f",&dt[0],&dt[1],&dt[2],
&dt[3],&dt[4],&dt[5],&dt[6],&dt[7],&dt[8],&dt[9],&dt[10],&dt[11]);

/* [HL] 2020-07-03 */
if (i!=12) {
	event_error("%%temperament expects 12 numbers");
	return;
}
for (i=0;i<12;i++) {
	if (fabs(dt[i]) > 200.0) {
		event_error("%%temperament detune cannot be greater than +- 200 cents");
		return;
	}
}
for (i=0;i<12;i++) {
	temperament_dt[i] = dt[i];
}
temperament = TEMPERDT;
}

void event_microtone(int dir, int a, int b)
{
int bend;
/* pitchwheel range +/- 2 semitones according to General MIDI
specification*/
/* resolution of 14bit -- order of bytes is inverted for pitchbend */
setmicrotone.num = dir*a; /* [SS] 2014-01-20 */
setmicrotone.denom = b;
if (a == 0) {bend = 8192; /* [SS] 2014-01-19 */
             microtone = 0; /* [SS] 2014-01-20 */
             setmicrotone.num = 0; /* [SS] 2014-01-25 */
             setmicrotone.denom = 1; /* [SS] 2025-01-06 */
             return;
            }
else {
  bend = dir*((int)(4096.0*a/b + 0.5))+8192; /* [HL] 2015-05-14 */
  bend = bend<0?0:(bend>16383?16383:bend);
  }
active_pitchbend = bend;
microtone=1;
if (b == 0) setmicrotone.denom = 1; /* [SS] 2025-01-06 */
return;
}


void event_normal_tone()
{ 
/* event_specific("MIDI", "pitchbend 0 64"); [SS] 2006-09-30 */
microtone = 0;
}

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

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

void event_handle_gchord(s)
/* handler for the guitar chords */
char* s;
{
  int basepitch;
  char accidental, accidental2, note; /* [SS] 2021-12-05 */
  char* p;
  char name[10]; /* JA increased from 9 to 10  2024-08-13 */
  int i;
  int chordno;
  int bassnote;
  int inversion;
  int mult;  /* [SS] 2021-12-05 */
  mult = 1;

  if (ignore_guitarchords == 1) return; /* [SS] 2019-12-09 */
  if ((*s >= '0') && (*s <= '5')) {
    event_finger(s);
    return;
  };
  p = s;
  if (*p == '(') p = p+1; /* ignore leading ( [SS] 2005-03-19*/
  if ((*p >= 'A') && (*p <= 'G')) {
    note = *p - (int) 'A' + (int) 'a';
    bassnote = 0;
    p = p + 1;
  } else {
    if ((*p >= 'a') && (*p <= 'g')) {
      note = *p;
      bassnote = 1;
      p = p + 1;
    } else {
      /* added SPACE to list [JM] 2018-02-22 */
      if (strchr("_^<>@" , (int)*p) == NULL) {
        if (!silent) event_error("Guitar chord does not start with A-G or a-g");
      };
      return;
    };
  };
  p = get_accidental(p, &accidental);
  p = get_accidental(p, &accidental2); /* [SS] 2021-12-05 */
  if (accidental2 != '=') mult = 2;  /* [SS] 2021-12-05 */
  basepitch = pitchof(note, accidental, mult, 0, 0) - middle_c;
  i = 0;
  while ((i<9) && (*p != ' ') && (*p != '\0') && (*p != '(')
               && (*p != '/') && (*p != ')')) {
/* also ignore closing parentheses ')'  [SS] 2005-03-19*/
    name[i] = *p;
    i = i+1;
    p = p + 1;
  };
  inversion = -1;
  if (*p == '/') {
    p = p + 1;
    if ((*p >= 'A') && (*p <= 'G')) {
      note = (int)*p - 'A' + 'a';
      p = p + 1;
      p = get_accidental(p, &accidental);
      p = get_accidental(p, &accidental2); /* [SS] 2021-12-05 */
      if (accidental2 != '=') mult = 2;  /* [SS] 2021-12-05 */
      inversion = pitchof(note, accidental, mult, 0, 0) - middle_c;
    } else if ((*p >= 'a') && (*p <= 'g')) {
      note = (int)*p;
      p = p + 1;
      p = get_accidental(p, &accidental);
      p = get_accidental(p, &accidental2); /* [SS] 2021-12-05 */
      if (accidental2 != '=') mult = 2;  /* [SS] 2021-12-05 */
      inversion = pitchof(note, accidental, mult, 0, 0) - middle_c;
    } else if (!silent) {
      event_error(" / must be followed by A-G or a-g in gchord");
    };
  };
  name[i] = '\0';
  chordno = getchordnumber(name);
  if (chordno == 0) {
    char msg[200];

    /* [SS] 2015-07-08 */
    sprintf(msg, "Unrecognized chord name \"%s\"\n(Hint: use %%MIDI chordname to define it. eg %%MIDI chordname sus4 0 4 7).", name);
    event_error(msg);
    chordno = 1; /* defaults to major */
  } else {
    /* only record voice as having chords if we recognize chord type */
    v->hasgchords = 1;
    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;
  char midimsg[40];

  p = s;
  /* remove any leading spaces */
  skipspace(&p);
  /* remove any trailing spaces */
  q = p;
  while ((*q != '\0') && (*q != ' ')) {
    q = q + 1;
  };
  if (*q == ' ') {
    *q = '\0';
  };
  done = 0;
/* add nofnop ... */
if (nofnop == 0) {
  if (strcmp(p, "ppp") == 0) {
    event_specific("MIDI", "beat 30 20 10 1", 0); /* [JA] 2024-04-30 and etc */
    done = 1;
  };
  if (strcmp(p, "pp") == 0) {
    event_specific("MIDI", "beat 45 35 20 1", 0);
    done = 1;
  };
  if (strcmp(p, "p") == 0) {
    event_specific("MIDI", "beat 60 50 35 1", 0);
    done = 1;
  };
  if (strcmp(p, "mp") == 0) {
    event_specific("MIDI", "beat 75 65 50 1", 0);
    done = 1;
  };
  if (strcmp(p, "mf") == 0) {
    event_specific("MIDI", "beat 90 80 65 1", 0);
    done = 1;
  };
  if (strcmp(p, "f") == 0) {
    event_specific("MIDI", "beat 105 95 80 1", 0);
    done = 1;
  };
  if (strcmp(p, "ff") == 0) {
    event_specific("MIDI", "beat 120 110 95 1", 0);
    done = 1;
  };
  if (strcmp(p, "fff") == 0) {
    event_specific("MIDI", "beat 127 125 110 1", 0);
    done = 1;
  };

  if ((strcmp(p,"crescendo(") == 0) || (strcmp(p,"<(") == 0) || 
      (strcmp(p,"crescendo)") == 0) || (strcmp(p,"<)") == 0)) {
          sprintf(midimsg,"beatmod %d",velocitychange);
          event_specific("MIDI", midimsg, 0); /* [JA] 2024-04-30 */
          done = 1;
   }

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

}; /* end nofnop */
  if (strcmp(p, "drum") == 0) {
    addfeature(DRUMON, 0, 0, 0);
    drumvoice = v->indexno;
    done = 1;
  };
  if (strcmp(p, "nodrum") == 0) {
    addfeature(DRUMOFF, 0, 0, 0);
    done = 1;
  };

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

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


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

/* [SS] 2011-10-19 */
  if (strcmp(p, "ped") == 0) {
    addfeature(PEDAL_ON, 0, 0, 0);
    done = 1;
  };

/* [SS] 2023-01-10 */
  if (strcmp(p, "ped(") == 0) {
    addfeature(PEDAL_ON, 0, 0, 0);
    done = 1;
  };


/* [SS] 2011-10-19  [SS] 2018-05-02*/
  if (strcmp(p, "ped-end") == 0) {
    addfeature(PEDAL_OFF, 0, 0, 0);
    if(quiet == -1) {
      sprintf(buff, "instruction !%s! is deprecated.\nUse !ped-up! instead", s);
      event_warning(buff);
      }
    done = 1;
  };

/* [SS] 2018-05-02 */
  if (strcmp(p, "ped-up") == 0) {
    addfeature(PEDAL_OFF, 0, 0, 0);
    done = 1;
  };

/* [SS] 2023-01-10 */
  if (strcmp(p, "ped)") == 0) {
    addfeature(PEDAL_OFF, 0, 0, 0);
    done = 1;
  };

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

 if (strcmp(s, "bend") == 0) {   /* [SS] 2012-12-11 */
   addfeature(EFFECT, 1, 0, 0);
   done = 1;
  };

 if (strcmp(s, "shape") == 0) { /* [SS] 2015-07-26 */
   addfeature(EFFECT, 2, 0, 0);
   done = 1;
  };

/* The following instructions were added to avoid numerous
   warnings if they appear in the abc file (in particular the
   files derived from Craig Sapp's kern files using hum2abc).
   Though most of these instructions are part of the abc
   2.2 standard, I have yet to implement them.
*/

 if (strcmp(s,"accent") == 0) { /* [SS] 2021-01-24 */
   done = 1;
   };

 if (strcmp(s,"mordent") == 0) { /* [SS] 2021-01-24 */
   done = 1;
   };

 if (strcmp(s,"sfz") == 0) { /* [SS] 2021-01-24 */
   done = 1;
   };

 if (strcmp(s,"wedge") == 0) { /* [SS] 2021-01-24 */
   done = 1;
   };

 if (strcmp(s,"turn") == 0) { /* [SS] 2021-01-25 */
   done = 1;
   };

  if (done == 0 && quiet == -1) {    /* [SS] 2013-11-02 */
    snprintf(buff, MAXLINE, "instruction !%s! ignored", s);
    /* [KG] 2022-01-13 static overflow */
    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,modmic)
/* apply modifiers to a set of accidentals */
struct voicecontext* v;
char modmap[7];
int modmul[7];
struct fraction modmic[7];
{
  int i;

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

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

  for (i=0; i<7; i++) {
    for (j=0;j<10;j++) {
      v->workmap[i][j] = v->basemap[i];
      v->workmul[i][j] = v->basemul[i];
    /* [SS] 2014-01-26 */
      v->workmic[i][j].num = v->basemic[i].num;
      v->workmic[i][j].denom = v->basemic[i].denom;
      }
   };
}

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

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

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

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


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

  /* find note to be tied */
  samechord = 0;
  newbar =0;  /* if 1 it allows pitchline[] == pitchline[] test */
  if (xinchord) samechord = 1;
  tienote = j;
  localvoiceno = voiceno;
  while ((tienote > 0) && (feature[tienote] != NOTE) &&
         (feature[tienote] != REST)) {
    tienote = tienote - 1;
    if (feature[tienote] == VOICE) break; /* [SS] 2010-01-15 */
  };
  if (feature[tienote] != NOTE) {
    event_error("Cannot find note before tie");
  } else {
    inchord = xinchord;
    /* change NOTE + TIE to TNOTE + REST */
    feature[tienote] = TNOTE;
    feature[j] = REST;

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

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

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

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

int j;
if (apply_fermata_to_chord && !ignore_fermata)   /* [SS] 2012-03-26 */
    {
    if (fermata_fixed) addfract(&num[end],&denom[end],1,1);
    else num[end] *= 2;
    }
for (j = from; j < end; j++)
  {
  /* [SS] 2015-04-17 also include REST which occurs in STACCATO CHORD */
  if (feature[j] == NOTE || feature[j] == TNOTE || feature[j] == REST) 
    {
      num[j] = num[end];
      denom[j] =denom[end]; 
    }
  }
apply_fermata_to_chord = 0;
}


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=1; /* [SDG] 2020-06-03 */
  int chord_start,chord_end;
  int voiceno;

  j = 0;
  inchord = 0;
  voiceno = 1;
  chord_num = -1;  /* [SS] 2019-08-11 ensure it is initialized */
  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);
      j = j + 1;
      break;
    case LINENUM:
      lineno = pitch[j];
      j = j + 1;
      break;
    case VOICE:
      voiceno = pitch[j];
    default:
      j = j + 1;
      break;
    };
  };
if (verbose >3) printf("tiefix finished\n");
}

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 cleargracenotes(int start,int end) {
removefeatures(start,end);
} /* [SS]  2021-01-24 */


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 = 1, next_denom = 1; /* [SDG] 2020-06-03 */
  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] == SINGLE_BAR || feature[j] == DOUBLE_BAR) {
                              cleargracenotes(start,end); /* [SS] 2021.01.24 */
                              return;
                             }      
    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 || feature[j] == CHORDOFFEX) {
        /*hostnoteend = j-1; [SS] 2009-10-27 */
        hostnoteend = 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) || (feature[p] == CHORDOFFEX)) {
        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 not
 * 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;
  int notesinchord;

  notesinchord = -1;
  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] == SINGLE_BAR || feature[j] == DOUBLE_BAR) {
                              cleargracenotes(start,end);
                              return;
                             } /* [SS] 2021-01-24 */    
    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 || feature[j] == CHORDOFFEX) {
      /*  hostnoteend = j-1;  [SS] 2009-10-27 */
      hostnoteend = 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) {
      switch(feature[p]) { /* [SS] 2021-03-30 */
      case NOTE:
      case REST:
        if (notesinchord <= 0) {
           /* if chordal grace note, only count the first note */
           grace_num = grace_num * denom[p] + grace_denom * num[p];
           grace_denom = grace_denom * denom[p];
           reduce(&grace_num, &grace_denom);
           if (notesinchord == 0) notesinchord++;
           }
        break; 
      case CHORDON:
        notesinchord = 0;
        break;
      case CHORDOFF:
        notesinchord = -1;
        break;
     default:
        break;
     }
      p = p + 1;
    };

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

/* is the following note long enough */
   p = hostnotestart;
   adjusted_num = num[p]*grace_denom*gfact_denom - denom[p]*grace_num;
   adjusted_den = denom[p]*grace_denom*gfact_denom;
   /* if (adjusted_den <=0.0)  not long enough [SS] 2014-10-09 */
   if (adjusted_num <=0.0) /* not long enough [SS] 2014-10-09 */
      {
      p = start;
      removefeatures(start,end); /* [SS] 2014-11-09 */
      /*while (p <= end)
       * { 
       * lenmul(p,0,1);
       * p = p+1;
	} */
      if (quiet == -1) event_warning("Grace sequence exceeds host note. Grace sequence cut off.");
      return;
      }

    /* adjust host note or notes */
    p = hostnotestart;
    while (p <= hostnoteend) {
      if ((feature[p] == NOTE) || (feature[p] == REST) || 
          (feature[p] == CHORDOFF) || (feature[p] == CHORDOFFEX)) {
        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;
  };
if (verbose >3) printf("dograce finished\n");
}

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 */
/* This function was reorganized on 2013-12-02 [SS] to 
   handle play on repeats when there are split voices.
*/
int type;
char* replist;
{
  int newtype;
  int depth;
  int voiceno, indexno;

  depth = splitdepth;
  /* [SS] 2015-03-25 */
  if (splitdepth > 0 && extended_overlay_running == 0) {
  /* we encountered the repeat while in a split voice;
     first recurse back to the original voice.
  */
  recurse_back_to_original_voice();
  }

  newtype = type;
  /* DOTTED_BAR introduced [SS] 2019-02-08 */
  if ((type == THIN_THICK) || (type == THICK_THIN) || (type == DOTTED_BAR)) {
    newtype = DOUBLE_BAR;
  };
  addfeature(newtype, 0, 0, 0);
  copymap(v);
  zerobar();
  if (strlen(replist) > 0) {
    event_playonrep(replist);
  };

/* If there are any split voices (voice overlays),  we need to
   put repeat symbol in all the split voices. This is all done
   by sync_voice() [SS] 2013-12-02.
*/
 {
 voiceno = v->voiceno;
 indexno = v->indexno;
 if (extended_overlay_running) return;
 /* [SS] 2015-03-25 */
 while (v->tosplitno != -1)
     { 
      v = getvoicecontext(v->tosplitno);
      splitdepth++;
      addfeature(VOICE, v->indexno, 0, 0);
      sync_voice (v,0,0);
     }
 if (v->fromsplitno != -1 || splitdepth >0) recurse_back_to_original_voice();
 }
 v->nbars = v->nbars + 1; /* [SS] 2019-03-18 */
}



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

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

static void fixreps()
/* find and correct missing repeats in music */
/* abc2midi places an extra || at the start of the music */
/* This can be converted to |: if necessary. */
{
  int j;
  int rep_point = 0; /* where to assume a repeat starts [SDG] 2020-06-03 */
  int expect_repeat;   /* = 1 after |: or ::  otherwise 0*/
  int use_next; /* if 1 set next bar line as ref_point */
  int nplays; /* counts PLAY_ON_REP associated with a BAR_REP*/

  expect_repeat = 0; 
  use_next = 0;
  nplays=0;
  j = 0;
  while (j < notes) {
    switch(feature[j]) {
    case SINGLE_BAR:           /*  |  */
      if (use_next) {
        rep_point = j;
        use_next = 0;
      };
      break;
    case DOUBLE_BAR:           /*  || */
      rep_point = j;
      use_next = 0;
      break;
    case BAR_REP:              /* |:  */
/* if |:  .. |: encountered. Report error and change second |: to :: */
      if (expect_repeat) {
          if (quiet == -1) event_error(" found another |: after a |:");
          placeendrep(j);
      };
      expect_repeat = 1;
      use_next = 0;
      break;
    case REP_BAR:                /* :| */
/* if :| encountered before |: place  "|:" at reference point */
       if ((!expect_repeat) && (nplays < 1)) {
          placestartrep(rep_point);
        };
      expect_repeat = 0; 
      rep_point = j;
      use_next = 0;
      break;

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

    case DOUBLE_REP:           /*  ::  */
      if (!expect_repeat) {
          if (quiet == -1) event_error(" found :: before |:");
          placestartrep(rep_point);
       };
      expect_repeat = 1;
      break;
    default:
      break;
    };
    j = j + 1;
  };
}


/* Barfly stress model support functions */

extern int segnum,segden; /* from genmidi.c */
extern int ngain[32];
extern int beatmodel;
/* [SS] 2011-08-17 */
void fdursum_at_segment(int segposnum, int segposden, int *val_num, int *val_den);

/* [SS] 2011-08-17 */
void beat_modifier (int i)
/* modifies the note durations inside a beat using Phil Taylor's model */
{
  int start_num,start_denom,startseg_num,startseg_denom;
  int segnumber;
  int end_num,end_denom,endseg_num,endseg_denom;
  int mstart_num,mstart_denom,mend_num,mend_denom;
  int delta_num,delta_denom;
/* start_num/start_denom is the original position of the note onset
 * end_num/end_denom is the original position of the end of the note
 * mstart_num/mstart_denom is the modified position of the note onset
 * mend_num/mend_denom is the modified position of the note end
 * startseg_num/startseg_denom is the position of the note onset in
 * segment units. eg. if a note starts in the middle of segment 1,
 * its value would be 3/2 (assuming we are counting from 0).
 */
  int inchord,notecount;
  notecount=0;
  inchord = 0;
  start_num=0;
  start_denom = 1;
  end_num=start_num;
  end_denom = start_denom;
  i++;
  while (feature[i] != SINGLE_BAR) {
    if (feature[i] == DOUBLE_BAR ||
        feature[i] == BAR_REP ||
        feature[i] == DOUBLE_REP ||
        feature[i] == REP_BAR) break;
    if (feature[i] == CHORDON) {
                               inchord = 1;
                               notecount = 0;
                               i++;
                               continue;
                               }
    if (feature[i] == CHORDOFF ||
        feature[i] == CHORDOFFEX) {
                               inchord = 0;
                               notecount = 0;
                               start_num = end_num;
                               start_denom = end_denom;
                               i++;
                               continue;
                               }
    if (feature[i] == NOTE || feature[i] == TNOTE ||
        (feature[i] == REST && pitch[i] ==0)) {
/* Care is needed for tied notes; they appear as TNOTE followed by*/
/* two REST. We want to ignore those two rests.*/
/* if REST and pitch[i] != 0 it is a tied note converted to a rest */
/* Hopefully we do not encounter tied rests. */
       if (notecount == 0) {
         addfract(&end_num,&end_denom,num[i],denom[i]);
         reduce(&end_num, &end_denom);
   /* Convert note positions to where they would map to after applying
      the duration modifiers. We map the positions to segment units and then
      transform them back to note units using the modified space fdursum.
      Since all units of time are represented as fractions, the operations
      are less transparent.
   */
   /* divide start_num/start_denom by segnum/segden */
   /* (divide note duration by 4 because they are 4 times larger in num/denom*/
         startseg_num = start_num*segden;
         startseg_denom = start_denom*segnum*4;
         reduce(&startseg_num,&startseg_denom);
   /* repeat for end_num and end_denom; */
         endseg_num = end_num*segden;
         endseg_denom = end_denom*segnum*4;
         reduce(&endseg_num,&endseg_denom);
         fdursum_at_segment(startseg_num,startseg_denom, &mstart_num, &mstart_denom);
         fdursum_at_segment(endseg_num,endseg_denom, &mend_num, &mend_denom);
   /*  now compute the new note length */
         delta_num = mend_num;
         delta_denom = mend_denom;
         addfract(&delta_num,&delta_denom,-mstart_num,mstart_denom);
         reduce(&delta_num,&delta_denom);
         if (inchord) notecount++;
         }
       if (verbose > 1) {
       printf("pitch %d from = %d/%d (%d/%d) to %d/%d (%d/%d) becomes %d/%d %d/%d",pitch[i],
start_num,start_denom,startseg_num,startseg_denom,end_num,end_denom,
endseg_num,endseg_denom,mstart_num,mstart_denom,mend_num,mend_denom);
       printf(" - %d/%d\n",delta_num,delta_denom);
       }
       num[i] = delta_num; 
       denom[i] = delta_denom;
       segnumber = startseg_num/startseg_denom; /* [SS] 2011-08-17 */
       stressvelocity[i] = ngain[segnumber];
         
       if (notecount == 0) {start_num = end_num;
                            start_denom = end_denom;}
       if (feature[i] == TNOTE) i++; /*skip following rest */
       }
    i++;
    }
}

/* [SS] 2011-08-24 */
static void apply_bf_stress_factors () {
  int j;
  char command[40];
  char inputfile[64];
  char *p;
  int barnumber;
  if (verbose) 
      printf("rhythmdesignator = %s\n",rhythmdesignator);

  if (stress_pattern_loaded == 0) {
    if (rhythmdesignator[0] == '\0') {
      event_error("No R: in header, cannot apply Barfly model without %%MIDI ptstress");
      return;
      }
    j = load_stress_parameters(rhythmdesignator);
    /* [SS] 2015-12-31 */
    if (j < 0 && beatmodel == 0) { /* [SS] 2018-04-16 */
       event_error("invalid R: designator");
       return;
       }
    }
  if (verbose)
      printf("beat_modifer using segment size %d/%d\n",segnum,segden);
  j = 0;
  barnumber = 0;
  while (j < notes) {
   switch(feature[j]) {
    case SINGLE_BAR:           /*  |  */
    case DOUBLE_BAR:           /*  || */
    case BAR_REP:              /*  |: */
    case REP_BAR:              /*  :| */
    case DOUBLE_REP:           /*  :: */
      barnumber++;
      if (verbose>1) printf("bar %d\n",barnumber);
      if (beatmodel == 2) beat_modifier(j);
    break;
    case DYNAMIC:
       p = atext[pitch[j]];
       skipspace(&p);
       readstr(command, &p, 40);
       if (strcmp(command, "stressmodel") == 0) {
          skipspace(&p);
          beatmodel = readnump(&p); /* [SS] 2011-08-23 */
          stressmodel=beatmodel;
          if (verbose) printf("%s %d\n",command,beatmodel);
          if (beatmodel == 1)
             return; /* do not apply beat_modifier */
         }
       if (strcmp(command, "ptstress") == 0) {
          skipspace(&p);
          strncpy(inputfile,p,60);
          if (verbose) printf("ptstress file = %s\n",inputfile);
          if (parse_stress_params (inputfile) == -1) readstressfile (inputfile);
          calculate_stress_parameters();
          beatmodel = 2;
          if (stressmodel !=0 && beatmodel != stressmodel) beatmodel = stressmodel;
         }
       break;
    default:
      break;
    }
  j = j+1;
  }
}



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

  if (verbose) {
    printf("scanning tune\n");
  };
  /* set up defaults */
  sf = 0;
  mi = 0;
  setmap(0, global.basemap, global.basemul);
  copymap(&global);
  global.octaveshift = 0;
  global.keyset = 0;
  voicecount = 0;
  head = NULL;
  for (j=0;j<64;j++) vaddr[j]=NULL; 
  v = NULL;
  got_titlename = 0;
  time_num = 4;
  time_denom = 4;
  mtime_num = 4;
  mtime_denom = 4;
  timesigset = 0;
  barchecking = 1;
  global.default_length = -1;
  event_tempo(default_tempo, 1, 4, 0, NULL, NULL);
  notes = 0;
  ntexts = 0;
  gfact_num = 1;
  gfact_denom = 4;
  hornpipe = 0;
  karaoke = 0;
  numsplits = 0;
  splitdepth = 0; /* [SS] 2010-02-07 */
  v1index = -1; /* [SS] 2010-02-07 */
  propagate_accidentals = default_retain_accidentals;
  fermata_fixed = default_fermata_fixed;
  /* [SS] 2016-01-02 */
  if (ratio_standard == -1) {
    ratio_a = default_ratio_a;
    ratio_b = default_ratio_b;
  } else {
    /* [SS] 2016-01-02 use celtic ratio for swing */
    ratio_a = 2;
    ratio_b = 4;
   }
  wcount = 0;
  parts = -1;
  middle_c = default_middle_c;
  temperament=TEMPERNORMAL; /* [HL] 2020-07-03 */
  for (j=0; j<26; j++) {
    part_start[j] = -1;
  };
  headerpartlabel = 0;
  initvstring(&part);
  for (j=0; j<16;j++) {
    channel_in_use[j] = 0;
  };
  set_gchords("x"); /* [SS] 2010-07-11 */
  gchordvoice = 0;
  set_drums("z");
  drumvoice = 0;
  wordvoice = 0;
  notesdefined = 1; /* [SS] 2012-07-02 */
  rhythmdesignator[0] = '\0'; /* [SS] 2015-12-31 */
  stress_pattern_loaded = 0; /* [SS] 2018-04-16 */
}

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 == 4) && (time_denom == 4)) {
    set_gchords("fzczfzcz");
  };
  if ( (time_num == 2)  && (time_denom == 4)) {
    set_gchords("fzcz");
  };
  if ( (time_num == 6)  && (time_denom == 4)) {
    set_gchords("fzczfzczfzcz");
  };
  if ((time_num == 3) && (time_denom == 8)) {
    set_gchords("fzczcz");
  };
  if ((time_num == 5) && ((time_denom == 8) || (time_denom == 4))) {
    set_gchords("fzbcz");
  };
  if ((time_num == 7) && ((time_denom == 8) || (time_denom == 4))) {
    set_gchords("fzbczcz");
  };
  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); [SS] 2014-10-29 */
  pastheader = 1;

  gracenotes = 0; /* not in a grace notes section */
  if (!timesigset) {
    event_warning("No M: in header, using default");
  };
  /* calculate time for a default length note */
  if (global.default_length == -1) {
    if (((float) time_num)/time_denom < 0.75) {
      global.default_length = 16;
    } else {
      global.default_length = 8;
    };
  };
/* ensure default_length is defined for all voices */
  p = head;
  while ((p != NULL)) {
    if (p->default_length == -1) p->default_length=global.default_length;
    p = p->next;
  };

  bar_num = 0;
  bar_denom = 1;
  set_meter(time_num, time_denom);
  if (hornpipe) {
    if ((time_denom != 4) || ((time_num != 2) && (time_num != 4))) {
      event_error("Hornpipe must be in 2/4 or 4/4 time");
      hornpipe = 0;
    };
  };

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

void event_key(sharps, s, modeindex, modmap, modmul, modmicrotone, gotkey, gotclef, clefname, clef,
          octave, transpose, gotoctave, gottranspose, explict)
/* handles a K: field */
int sharps; /* sharps is number of sharps in key signature */
char *s; /* original string following K: */
int modeindex; /* 0 major, 1,2,3 minor, 4 locrian, etc.  */
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;
char* clefname;
cleftype_t *clef;  /* [JA] 2020-10-19 */
int octave, transpose, gotoctave, gottranspose;
int explict;
{
  int minor;
  minor =0;
  if (modeindex >0 && modeindex <4) minor = 1;
  if ((dotune) && gotkey) {
    if (pastheader) {
      if (!explict) setmap(sharps, v->basemap, v->basemul); /* [SS] 2010-05-08*/
      altermap(v, modmap, modmul,modmicrotone);
      copymap(v);
      addfeature(KEY, sharps, 0, minor);
      if (gottranspose) {
        addfeature(TRANSPOSE, transpose, 0, 0);
      };
    } else {
      if (gottranspose) {
        addfeature(GTRANSPOSE, transpose, 0, 0);
      };
      if (!explict) setmap(sharps, global.basemap, global.basemul); /* [SS] 2010-05-08 */
      altermap(&global, modmap, modmul,modmicrotone);
      global.keyset=1;
      copymap(&global);
      sf = sharps;
      mi = minor;

      /* [SS] 2021-06-24 
      ***headerprocess();

      ***v = getvoicecontext(1);
      ***if (!inbody) v1index = notes; /* save position in case of split voice */
 
    if (gotclef)
    {
      event_octave(clef->octave_offset, 0);
    }
    if (gotoctave) {
      event_octave(octave,0);
    };
    /* [SS] 2021-06-24 */
    headerprocess();
    v = getvoicecontext(1);
    if (!inbody) v1index = notes; /* save position in case of split voice */
    };
  };
}



/* Handling missing repeats in multivoiced and multipart abc tune */


int voicestart[64];
int bar_rep_found[64];
int add_leftrepeat_at[100];
int num2add;

void add_missing_repeats (); 

void clear_voice_repeat_arrays () {
int i;
for (i=0;i<64;i++) {
   voicestart[i] = 0;
   bar_rep_found[i] = 0;
   }
}
  


void scan_for_missing_repeats ()
{
/* The function attempts to clean up the missing left repeats |:
 * that occur in many multivoiced or multipart abc tunes.
 * The function scans the feature[] representation and
 * keeps track where the voice appears for the first time
 * in an individual part. If a :| appears in that voice,
 * but no |: was detected, then the function inserts a |:
 * at the point where the voice first appears.
 */
int i,j;
int voicenum;
char part;
num2add = 0; /* 2010-02-06 */
part = '0';
voicenum = 0;
clear_voice_repeat_arrays(); /* [SS] 2012-03-22 */
/* set voicestart[0] in case there are no voices or parts */
for (i=0;i<notes;i++) {
  j = feature[i];
  if (j == MUSICLINE) {
     insertfeature(DOUBLE_BAR,0,0,0,i+1);
     voicestart[0] = i+1; 
     break;
     }
  }
for (i=0;i<notes;i++) {
  j = feature[i];
  if (j == PART && parts != -1) /* [SS] 2013-03-14 */
                 {clear_voice_repeat_arrays();
                  part = (char) pitch[i];
                  voicestart[0] = i;
                 }
  if (j == VOICE) {
        voicenum = pitch[i];
        if(!voicestart[voicenum]) {
          voicestart[voicenum] = i;
          /*printf("voicestart[%d] = %d\n",voicenum,voicestart[voicenum]);*/
          }
        }
  if (j == BAR_REP) bar_rep_found[voicenum] = 1;
  if ((j == REP_BAR || j == DOUBLE_REP) && (!bar_rep_found[voicenum])) {
    /* printf("missing BAR_REP for voice inserted for voice %d part %c\n",voicenum,part); [SS] 2011-04-19 */
     add_leftrepeat_at[num2add] = voicestart[voicenum]; /* [SS] 2014-10-31 2021-12-21*/
     num2add++;
     bar_rep_found[voicenum] = 1;
     }
  }
if (num2add > 0) 
 add_missing_repeats (); 
if (verbose >3) printf("scan_for_missing_repeats finished\n");
}


void add_missing_repeats () {
int i,j,k;
int leftrepeat;
for (i = num2add-1; i >= 0; i--) {
leftrepeat = add_leftrepeat_at[i];
k=0;
/* [SS] 2021-12-21 */
if (voicesused) { /* 2021-02-21 */
while (feature[leftrepeat] != VOICE && k < 20) {
  leftrepeat++; 
  k++;
  }
}
 insertfeature(BAR_REP,0,0,0,leftrepeat+1); 
 /* for (j=0;j<=parts;j++) {   [SS] 2011-06-06 */
 for (j=0;j<26;j++) {  /* [SS] 2011-08-03 */
   if (part_start[j] > add_leftrepeat_at[i])
     part_start[j]++;
     }
  }
}

/* [SS] 2012-11-23 */
void convert_tnote_to_note (int loc) {
/* tied notes TNOTE are handled in a different
   manner by genmidi so we need to change it
   to NOTE so it expand_ornament can process
   it. We change TNOTE to NOTE and eliminate the
   RESTS associated with TNOTE
*/
int i,j,pitchflag;
feature[loc] = NOTE;
/* Look ahead and remove the REST associated with TNOTE    */
/* The last REST in the TNOTE sequence has a nonzero pitch */
j=loc;
for (i=0;i<6;i++) {
  if(feature[j] == REST) {
    pitchflag = pitch[j];
    removefeature(j);
    if (pitchflag != 0) break;
    } else j++; 
  }
}



/* [SS] 2012-05-30  2012-11-23 */
void expand_ornaments () {
int i;
struct notestruct *s;
int notetype,deco_index;
for (i=0;i<notes;i++) {
  if (decotype[i] != 0 && feature[i] == TNOTE) convert_tnote_to_note(i);
  if (decotype[i] != 0 && feature[i] == NOTE) {
      deco_index = decotype[i];
      s =  noteaddr[deco_index];
      notetype = s->notetype;
      switch (notetype) {
         case TRILL:
           dotrill_output(i);
           break;
         case ROLL:
           doroll_output(i);
           break;
         default: printf("no such decoration %d\n",decotype[i]);
         }
      }
  }
/*dump_notestruct();*/
if (verbose> 3) printf("expand_ornaments finished\n");
 }

/* [SS] 2019-01-01 */
void check_for_timesig_preceding_bar_line () {
/* The time signature placed before the next measure can
 * effect the current measure and result in warnings.    */
int i;
for (i=4;i<notes;i++) {
	if (feature[i] == TIME && feature[i+1] == SINGLE_BAR)
		interchange_features(i,i+1);
        }
}

/* [SS] 2012-12-25 */
void fix_part_start () 
/* update part_start array in case things have moved around */
{
int i;
int partnum;
for (i=0;i<notes;i++) {
  if (feature[i] == PART) {
    /*printf("%d PART %d\n",i,pitch[i]); */
    partnum = pitch[i]-65;
    if (partnum >= 0 && partnum < 26) part_start[partnum] = i;
    }
  }
}


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

  complete_all_split_voices ();
  /* dump_voicecontexts(); for debugging*/
  setup_trackstructure();
  clearvoicecontexts();
  /* init_drum_map(); [SS] 2017-12-10 moved to main() */
  if (!pastheader) {
    event_error("No valid K: field found at start of tune");
  } else {
    scan_for_missing_repeats();

    if (parts > -1) {
      addfeature(PART, ' ', 0, 0);
    };
    if (headerpartlabel == 1 && !silent) {
      event_error("P: field in header should go after K: field");
    };
    if (verbose > 1) {
      printf("handling grace notes\n");
    };
    dograce();
    if (barflymode) apply_bf_stress_factors (); /* [SS] 2011-08-24 */ 
    tiefix(); /* [SS] 2014-04-03 */
    if ((parts == -1) && (voicecount == 1)) {
      if (verbose > 1) {
        printf("fixing repeats\n");
      };
      fixreps();
    };

 
    expand_ornaments();

    check_for_timesig_preceding_bar_line (); /* [SS] 2019-01-01 */ 

    no_more_free_channels = 0;

    if (parts >= 0) fix_part_start(); /* [SS] 2012-12-25 */
    if (verbose > 5) dumpfeat(0,notes);

    if (check) {
      Mf_putc = nullputc;
      header_time_num = time_num; /* [SS] 2010-05-21 */
      header_time_denom = time_denom; /* [SS] 2010-05-21 */
      Mf_numbyteswritten = 0; /* [SS] 2019-03-23 */
      if (ntracks == 1) {
        writetrack(0);
      } else {
        if (!done_with_barloc) {
            diaghandle = fopen("barloc.txt","w");
            for (i=0; i<ntracks; i++) {
               writetrack(i);
	       dump_barloc(diaghandle,i);
	    }
	    fclose(diaghandle);
	    done_with_barloc = 1;
        } else {  /* done_with_barloc == 1 */
            for (i=0; i<ntracks; i++) {
               writetrack(i);
	    }	
        }
      } /* more than 1 track */
    } else {    /* check != 0 */
      if ((fp = fopen(outname, "wb")) == NULL) {
        event_fatal_error("File open failed");
      };
      if (!silent) 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);
    free_notestructs(); /* [SS] 2012-06-03 */ 
  };
}

void event_blankline()
/* blank line found in abc signifies the end of a tune */
{

  if (dotune) {
    if (!silent) print_voicecodes();
    finishfile();
    parseroff();
    dotune = 0;
  };
}

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

  started_parsing = 1;
  bodystarted =0; /* [SS] 2011-01-01 */
  if (dotune) {
    finishfile();
    parseroff();
    dotune = 0;
  };
  if ((n == xmatch) || (xmatch == 0) || (xmatch == -1)) {
    if (xmatch == -1) {
      xmatch = -2;
    };
    parseron();
    dotune = 1;
     
    /* [SS] 2019-08-11
      v = newvoice(1);
      head = v;
      vaddr[0] = v;
      vaddr[v->indexno] = v;
    */

    pastheader = 0;
    if (userfilename == 0) {
      if (outname != NULL) {
        free(outname);
      };
      sprintf(numstr, "%d", n);
      if ( (int) strlen(numstr) > namelimit - 1) {
        numstr[namelimit - 1] = '\0';
      };
      if ((int) (strlen(outbase) + strlen(numstr)) > namelimit) {
        strncpy(newname, outbase, namelimit - strlen(numstr));
        strcpy(&newname[namelimit - strlen(numstr)], numstr);
        strcpy(&newname[strlen(newname)], ".mid");
      } else {
        sprintf(newname, "%s%s.mid", outbase, numstr);
      };
      outname = addstring(newname);
      /* outname = (char*)checkmalloc(strlen(outbase) + 22 + strlen(".mid")); */
      /* sprintf(outname, "%s%d.mid", outbase, n); */
    };
    startfile();
  };
}

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

void set_control_defaults(); /* from queues.c */

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

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

  event_init(argc, argv, &filename);
  /* [SS] 2013-04-10 */
  if (csmfilename != NULL) read_custom_stress_file(csmfilename);
  if (argc < 2) {
    /* printf("argc = %d\n", argc); */
  } else {
    init_abbreviations();
    init_drum_map(); /* [SS] 2017-12-10 */
    if (!silent) printf("%s\n",VERSION); /* [SS] 2015-07-15 */
    parsefile(filename);
    free_abbreviations();
  };
  return(0);
}

