/*************************************************
*    The PMW Music Typesetter - 3rd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2009 */

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: January 2009 */


/* This file contains part IV of the code for reading in a PMW score file. It
contains code for handling stave directives. The main function is at the 
bottom, preceded by a table of directives, which refer to the other functions.
They all have the same interface: no arguments or results. The variable 
read_dir is set to point to the found directive, and in its data structure 
there may be up to two arguments. The action of each function is either to set
up a new item and add it to the list (done by store_getitem()), and if
necessary, fill in its data value(s), or to set flags or variables that affect 
the way the stave's data is to be read. */


#include "pmwhdr.h"
#include "readhdr.h"



/*************************************************
*              Static variables                  *
*************************************************/

static uschar real_clef[] = {
  clef_treble, clef_soprano, clef_mezzo, clef_alto, clef_tenor,
  clef_cbaritone, clef_baritone, clef_bass, clef_deepbass, 
  clef_treble, clef_treble, clef_treble, clef_treble, clef_treble, 
  clef_bass, clef_bass };

static int clef_octave[] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, -12, -12, 12, -12 };

static int read_assumeflag = FALSE;



/*************************************************
*        Common routine for dataless item        *
*************************************************/

static void 
p_common(void)
{
(void)store_getitem(read_dir->arg1);
}


/*************************************************
*         Common routine for above/below         *
*************************************************/

static void 
p_above(void)
{
int flag = FALSE;
b_charvaluestr *p = store_getitem(read_dir->arg1);
uschar word[80];
read_word(word);
if (Ustrcmp(word, "above") == 0) flag = TRUE;
  else if (Ustrcmp(word, "below") != 0) error_moan(10, "\"above\" or \"below\"");
p->value = flag;
}


/*************************************************
*       Common routine for positive dimension    *
*************************************************/

static void 
p_pvalue(void)
{
sigch();
if (isdigit(read_ch))
  {
  b_intvaluestr *p = store_getitem(read_dir->arg1);
  p->value = read_integer(TRUE);
  }
else error_moan(10, "Number");
}


/*************************************************
*       Common routine for signed dimension      *
*************************************************/

static void 
p_svalue(void)
{
int x;
b_intvaluestr *p;
if (!read_expect_integer(&x, TRUE, TRUE)) return;
p = store_getitem(read_dir->arg1);
p->value = x;
}


/*************************************************
*        Common routine for font setting         *
*************************************************/

static void 
p_font(void)
{
int *p = (read_dir->arg1 == 0)? &stave_fbfont :
         (read_dir->arg1 == 1)? &stave_textfont :
         (read_dir->arg1 == 2)? &stave_ulfont : &stave_olfont;
int f = font_fontword(FALSE);
if (f >= 0) *p = f;
}


/*************************************************
*        Common routine for size setting         *
*************************************************/

static void 
p_size(void)
{
int size;
int *p = (read_dir->arg1 == 0)? &stave_fbsize :
         (read_dir->arg1 == 1)? &stave_textsize :
         (read_dir->arg1 == 2)? &stave_ulsize : &stave_olsize;
if (!read_expect_integer(&size, FALSE, FALSE)) return;
if (--size < 0 || size >= MaxTextFont)
  { error_moan(39, MaxTextFont); return; }
*p = size;
}


/*************************************************
*     Common routine for fixed noteheads value   *
*************************************************/

static void 
p_nh(void)
{
b_noteheadsstr *p = store_getitem(b_noteheads);
p->value = read_dir->arg1;
stave_stemflag = nf_stem;
}


/*************************************************
*             Beamacc and Beamrit                *
*************************************************/

static void 
p_beamaccrit(void)
{
b_charvaluestr *p = store_getitem(read_dir->arg1);
sigch();
if (isdigit(read_ch))
  {
  int n = read_integer(FALSE);
  if (n != 2 && n != 3) error_moan(10, "2 or 3");
    else stave_accritvalue = n;
  }
p->value = stave_accritvalue;
}


/*************************************************
*               Clef setting                     *
*************************************************/

static void 
p_clef(void)
{
if (!read_assumeflag)
  {
  b_clefstr *p = store_getitem(b_clef);
  p->trueclef = read_dir->arg1;
  p->suppress = FALSE;
  }
else
  {
  b_setclefstr *p = store_getitem(b_setclef);
  p->value = read_dir->arg1;
  read_assumeflag = FALSE;
  }
stave_clef = real_clef[read_dir->arg1];
stave_clef_octave = clef_octave[read_dir->arg1];
sigch();
if (isdigit(read_ch) || read_ch == '-')
  {
  read_expect_integer(&stave_octave, FALSE, TRUE);
  stave_octave *= 12;
  }
stave_lastbasenoteptr = NULL;   
}


/*************************************************
*                Assume                          *
*************************************************/

static char *assume_list[] = {
  "alto", "baritone", "bass", "contrabass", "deepbass", 
  "hclef", "key", "mezzo", "noclef", "soprabass", "soprano",
  "tenor", "time", "treble", "trebledescant", "trebletenor", 
  "trebletenorb" };

static void 
p_assume(void)
{
int i;
read_word(read_stavedir);

for (i = 0; i < (sizeof(assume_list)/sizeof(uschar *)); i++)
  if (Ustrcmp(read_stavedir, assume_list[i]) == 0)
    { read_assumeflag = TRUE; return; }

read_stavedir[0] = 0;
error_moan(10, "Clef, key, or time setting");
}


/************************************************
*           Barlinestyle                        *
************************************************/

static void 
p_barlinestyle(void)
{
(void)read_expect_integer(&stave_barlinestyle, FALSE, FALSE);
read_barlinestyle = stave_barlinestyle;  /* For current bar */

/* The default, for use with totally empty bars, is the first style given. This
isn't entirely satisfactory, but copes with most cases. */

if (stavehead->barlinestyle == 255) 
  stavehead->barlinestyle = stave_barlinestyle;
}


/*************************************************
*               Barnumber                        *
*************************************************/

static void 
p_barnum(void)
{
b_barnumstr *p = store_getitem(b_barnum);
int flag = TRUE;
int x = 0;
int y = 0;

sigch();
if (read_ch == '/')
  {
  while (read_ch == '/')
    {
    int sign, *a = NULL, b;

    next_ch();
    switch (read_ch)
      {
      case 'u': sign = +1; a = &y; break;
      case 'd': sign = -1; a = &y; break;
      case 'l': sign = -1; a = &x; break;
      case 'r': sign = +1; a = &x; break;

      default:
      sign = 0;
      error_moan(10, "/u, /d, /l, or /r");
      break;
      }

    if (sign == 0) break;
    next_ch();
    if (!read_expect_integer(&b, TRUE, TRUE)) break;

    *a = *a + sign *b;
    }
  }
else if (isalpha(read_ch))
  {
  read_word(read_stavedir);
  if (Ustrcmp(read_stavedir, "off") == 0)
    {
    flag = FALSE;
    read_stavedir[0] = 0;
    }
  }

p->flag = flag;
p->x = x;
p->y = y;
}


/*************************************************
*                Couple                          *
*************************************************/

static void 
p_couple(void)
{
uschar word[80];
read_word(word);
if (Ustrcmp(word, "up") == 0) stave_couplestate = +1;
else if (Ustrcmp(word, "down") == 0) stave_couplestate = -1;
else if (Ustrcmp(word, "off") == 0) stave_couplestate = 0;
else error_moan(10, "\"up\", \"down\", or \"off\"");
}


/*************************************************
*                 Cue                            *
*************************************************/

static void 
p_cue(void)
{
stave_noteflags &= ~nf_cuedotalign;
stave_noteflags |= nf_cuesize;
sigch();
if (read_ch == '/')
  {
  uschar word[80];
  next_ch(); 
  read_word(word);
  if (Ustrcmp(word, "dotalign") == 0) stave_noteflags |= nf_cuedotalign;
  else error_moan(10, "\"dotalign\"");   
  } 
}


/*************************************************
*                 Dots                           *
*************************************************/

static void 
p_dots(void)
{
uschar word[80];
read_word(word);
if (Ustrcmp(word, "above") == 0) stave_noteflags &= ~nf_lowdot;
else if (Ustrcmp(word, "below") == 0) stave_noteflags |= nf_lowdot;
else error_moan(10, "\"above\" or \"below\"");
}


/*************************************************
*                 Draw                           *
*************************************************/

static void 
p_draw(void)
{
tree_node *node;
int argcount = 0;
drawitem args [20];
uschar word[80];

sigch();
while (isdigit(read_ch) || read_ch == '-' || read_ch == '+' || read_ch == '\"')
  {
  if (read_ch == '\"') 
    { 
    args[++argcount].d.ptr = read_draw_text();
    args[argcount].dtype = dd_text; 
    } 
  else 
    {
    if (!read_expect_integer(&(args[++argcount].d.val), TRUE, TRUE)) break;
    args[argcount].dtype = dd_number; 
    } 
  sigch();
  }

read_word(word);
node = Tree_Search(draw_tree, word);
if (node == NULL) error_moan(70, word); else
  {
  b_drawstr *d = store_getitem(b_draw);
  d->overflag = read_dir->arg1;
  d->item = node;
  if (argcount == 0) d->args = NULL; else
    {
    int i;
    d->args = store_Xget((argcount+1)*sizeof(drawitem));
    d->args[0].dtype = dd_number; 
    d->args[0].d.val = argcount;
    for (i = 1; i <= argcount; i++) d->args[i] = args[i];
    }
  }
}


/*************************************************
*                 Endcue                         *
*************************************************/

static void 
p_endcue(void)
{
stave_noteflags &= ~(nf_cuesize|nf_cuedotalign);
}


/*************************************************
*             Endline & Endslur                  *
*************************************************/

static void 
p_endline(void)
{
int id = 0;
sigch();
if (read_ch == '/')
  {
  next_ch();
  if (read_ch != '=') error_moan(10, "\"=\""); else
    {
    next_ch();
    id = read_ch;
    next_ch();
    }
  }

if (stave_slurcount-- > 0)
  {
  b_endslurstr *p = store_getitem(b_endslur);
  p->id = id;
  }
else
  {
  error_moan(17, "end of slur or line - ignored");
  stave_slurcount = 0;
  }
}


/*************************************************
*                  Endstave                      *
*************************************************/

/* This sets the global read_endstave flag, which is detected in the
stave-reading code. */

static void 
p_endstave(void)
{
read_endstave = TRUE;
}


/*************************************************
*              Footnote                          *
*************************************************/

static void 
p_footnote(void)
{
b_footnotestr *f = store_getitem(b_footnote);
f->type = b_footnote;
read_headfootingtext(&(f->h), rh_footnote);
}


/*************************************************
*                 Hairpins                       *
*************************************************/

static void 
p_hairpins(void)
{
uschar word[80];
stave_hairpinflags = stave_hairpiny = 0;
read_word(word);
if (Ustrcmp(word, "below") == 0) stave_hairpinflags = hp_below;
  else if (Ustrcmp(word, "middle") == 0)
    stave_hairpinflags = hp_below | hp_middle;
      else if (Ustrcmp(word, "above") != 0)
        {
        error_moan(10, "\"above\", \"below\", or \"middle\"");
        return;
        }

/* Default adjustment is allowed for all three positions */

sigch();
if (read_ch == '+' || read_ch == '-')
  {
  (void)read_expect_integer(&stave_hairpiny, TRUE, TRUE);
  }

/* Absolute value is allowed only for above and below */

else if ((stave_hairpinflags & hp_middle) == 0 && isdigit(read_ch))
  {
  stave_hairpinflags |= hp_abs;
  stave_hairpiny = read_integer(TRUE);
  if ((stave_hairpinflags & hp_below) != 0) stave_hairpiny = -stave_hairpiny;
  }
}


/*************************************************
*               Hairpinwidth                     *
*************************************************/

static void 
p_hairpinwidth(void)
{
(void)read_expect_integer(&stave_hairpinwidth, TRUE, FALSE);
}


/*************************************************
*                 Key                            *
*************************************************/

static void 
p_key(void)
{
int warn = curmovt->keywarn;
int oldkey = stave_key_tp;
stave_key = read_key();
read_initbaraccs(baraccs, stave_key);

stave_key_tp = transpose_key(stave_key, stave_transpose, TRUE);

read_initbaraccs(baraccs_tp, stave_key_tp);

read_word(read_stavedir);
if (Ustrcmp(read_stavedir, "nowarn") == 0)
  {
  read_stavedir[0] = 0;
  warn = FALSE;
  }

if (!read_assumeflag)
  {
  b_keystr *p = store_getitem(b_key);

  if (stave_key_tp == 2 || stave_key_tp == 21)
    {
    p->key = oldkey | 64;
    p->warn = warn;
    p->suppress = FALSE;
    p = store_getitem(b_key);
    }

  p->key = stave_key_tp;
  p->warn = warn;
  p->suppress = FALSE;
  }
else
  {
  b_setkeystr *p = store_getitem(b_setkey);
  p->value = stave_key_tp;
  read_assumeflag = FALSE;
  }
}


/*************************************************
*                 Justify                        *
*************************************************/

static void 
p_justify(void)
{
uschar word[80];
sigch();
if (read_ch == '+' || read_ch == '-')
  {
  while (read_ch == '+' || read_ch == '-')
    {
    b_justifystr *p;
    int opt = read_ch;
    int side;
    next_ch();
    read_word(word);
    sigch();
    if (Ustrcmp(word, "top") == 0) side = just_top;
    else if (Ustrcmp(word, "bottom") == 0) side = just_bottom;
    else if (Ustrcmp(word, "left") == 0) side = just_left;
    else if (Ustrcmp(word, "right") == 0) side = just_right;
    else
      {
      error_moan(10, "\"top\", \"bottom\", \"left\", or \"right\"");
      return;
      }
    p = store_getitem(b_justify);
    p->opt = opt;
    p->side = side;
    }
  }
else error_moan(10, "\"+\" or \"-\"");
}


/*************************************************
*             Linegap & Slurgap                  *
*************************************************/

static void 
p_linegap(void)
{
b_linegapstr *p;
tree_node *draw = NULL;
gaptextstr *gaptext = NULL;
drawitem *drawargs = NULL;
int lineid = 0;
int xadjust = 0;
int hfraction = -1;
int width = -1;

/* Read the options */

sigch();
while (read_ch == '/')
  {
  int x;
  next_sigch();
  switch (read_ch)
    {
    case '=':
    next_ch(); lineid = read_ch; next_sigch();
    break;

    case 'd':
    if (Ustrncmp(read_chptr, "raw ", 4) == 0)
      {
      int argcount = 0;
      drawitem args[20];
      uschar word[80];
      read_chptr += 4;
      read_ch = ' ';
      sigch();
      while (isdigit(read_ch) || read_ch == '-' || read_ch == '+' || read_ch == '\"')
        {
        if (read_ch == '\"') 
          {
          args[++argcount].d.ptr = read_draw_text();
          args[argcount].dtype = dd_text; 
          } 
        else 
          {
          if (!read_expect_integer(&(args[++argcount].d.val), TRUE, TRUE)) break;
          args[argcount].dtype = dd_number; 
          } 
        sigch();
        }
      if (argcount > 0)
        {
        int i;
        drawargs = store_Xget((argcount+1)*sizeof(drawitem));
        drawargs[0].dtype = dd_number; 
        drawargs[0].d.val = argcount;
        for (i = 1; i <= argcount; i++) drawargs[i] = args[i];
        }
      read_word(word);
      draw = Tree_Search(draw_tree, word);
      if (draw == NULL) error_moan(70, word);
      }
    else error_moan(10, "\"draw\"");
    break;

    case 'h':
    next_ch();
    if (isdigit(read_ch))
      {
      if (!read_expect_integer(&x, TRUE, FALSE)) return;
      hfraction = x;
      }
    else hfraction = 500;
    break;

    case 'l':
    next_ch();
    if (!read_expect_integer(&x, TRUE, FALSE)) return;
    xadjust -= x;
    break;

    case 'r':
    next_ch();
    if (!read_expect_integer(&x, TRUE, FALSE)) return;
    xadjust += x;
    break;

    case 'w':
    next_ch();
    if (!read_expect_integer(&x, TRUE, FALSE)) return;
    width = x;
    break;
    
    case '\"':
    gaptext = store_Xget(sizeof(gaptextstr)); 
    gaptext->text = string_check(string_read());
    gaptext->flags = 0;
    gaptext->size = 0;
    gaptext->x = 0;
    gaptext->y = 0;  

    while (read_ch == '/')
      {
      int size;
      next_ch();
      switch (read_ch)
        {
        case 'b':
        if (Ustrncmp(read_chptr, "ox", 2) == 0)
          {
          next_ch();
          next_ch();
          next_ch();
          gaptext->flags |= text_box;
          }
        else error_moan(10, "/box, /ring, or /s");
        break;
        
        case 'd':
        gaptext->y -= read_movevalue();
        break;   
    
        case 'l':
        gaptext->x -= read_movevalue();
        break;   
    
        case 's':
        next_ch();
        read_expect_integer(&size, FALSE, FALSE);
        if (--size < 0 || size >= MaxTextFont)
          { error_moan(39, MaxTextFont); size = 0; }
        gaptext->size = size;
        break;
    
        case 'r':
        if (Ustrncmp(read_chptr, "ing", 3) == 0)
          {
          next_ch();
          next_ch();
          next_ch();
          next_ch();
          gaptext->flags |= text_ring;
          }
        else gaptext->x += read_movevalue();
        break;   
    
        case 'u':
        gaptext->y += read_movevalue();
        break;   

        default:
        error_moan(10, "/box, /ring, or /s");
        break;
        }
      }
    break;  

    default:
    error_moan(10, "=, l, r, or w");
    break;
    }

  sigch();
  }

if (stave_slurcount <= 0) 
  error_moan(17, "%sgap directive", (read_dir->arg1)? "slur":"line");

/* Width defaults to width of text or 4 points */

if (width < 0)
  {
  if (gaptext == NULL) width = 4000; else
    {
    int fontsize = mac_muldiv((curmovt->stavesizes)[curstave], 
      ((curmovt->fontsizes)->fontsize_text)[gaptext->size], 1000);
    int *matrix = ((curmovt->fontsizes)->fontmatrix_text)[gaptext->size];
    if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int));
    width = font_stringwidth(gaptext->text, font_rm, fontsize) + fontsize;
    font_reset(); 
    }
  }      

/* Get data block and fill it in. */

p = store_getitem(b_linegap);
p->type = (read_dir->arg1)? b_slurgap : b_linegap;
p->id = lineid;
p->hfraction = hfraction;
p->xadjust = xadjust;
p->width = width;
p->draw = draw;
p->args = drawargs;
p->gaptext = gaptext;
}


/*************************************************
*      Midichannel, Midipitch, Midivoice,        *
*         Playvolume, Playtranspose              *
*************************************************/

/* These are all variations on the same theme. All of them create a play change
item entry, with various different parameters. We start with a local subrouting 
that they can all use.

Arguments:
  channel     channel number
  void        voice number
  note        note pitch
  volume      volumne
  transpose   transpose value
  
Returns:      nothing     
*/

static void 
makechange(int channel, int voice, int note, int volume, int transpose)
{
b_playchangestr *p = store_getitem(b_playchange);
p->stave = curstave;
p->barno = stave_barnumber;
p->channel = channel;
p->voice = voice;
p->note = note;
p->volume = volume;
p->transpose = transpose;
p->next = NULL;
*read_lastplaychange = p;
read_lastplaychange = &(p->next);
}

/*** Midichannel ***/

static void 
p_midichannel(void)
{
int channel;
int voicenumber;
int volume = 128;
uschar string[80];
if (!read_expect_integer(&channel, FALSE, FALSE)) return;
if (channel < 1 || channel > midi_maxchannel)
  {
  error_moan(109, midi_maxchannel);
  return;
  }

if (read_plainstring(string))
  {
  if (string[0] == 0) voicenumber = 129; else /* => no change */
    {
    if (string[0] == '#') voicenumber = Uatoi(string+1);
      else voicenumber = read_getmidinumber(midi_voicenames, string, US"voice");
    if (voicenumber < 1 || voicenumber > 128)
      {
      error_moan(109, "voice", 128);
      voicenumber = 1;
      }
    }

  if (read_ch == '/')
    {
    int vol;
    next_ch();
    if (read_expect_integer(&vol, FALSE, FALSE))
      {
      if (vol > 15) 
        error_moan(10, "Number between 0 and 15"); else volume = vol;
      }
    }
  }
else voicenumber = 129;

makechange(channel, voicenumber - 1, 128, volume, 0);  /* 128 => no change */
}

/*** Midivoice ***/

static void 
p_midivoice(void)
{
int voicenumber;
uschar string[80];
if (read_plainstring(string))
  {
  if (string[0] == 0) voicenumber = 129; else  /* => no change */
    {
    if (string[0] == '#') voicenumber = Uatoi(string+1);
      else voicenumber = read_getmidinumber(midi_voicenames, string, US"voice");
    if (voicenumber < 1 || voicenumber > 128)
      {
      error_moan(109, "voice", 128);
      voicenumber = 1;
      }
    }
  makechange(128, voicenumber - 1, 128, 128, 0);  /* 128 => no change */
  }
else error_moan(10, "string");
}

/*** Midipitch ***/

static void 
p_midipitch(void)
{
int note;
uschar string[80];
if (read_plainstring(string))
  {
  if (string[0] == 0) note = 0;  /* => no more forcing */
    else if (string[0] == '#') note = Uatoi(string+1);
      else note = read_getmidinumber(midi_percnames, string, US"percussion instrument");
  makechange(128, 128, note, 128, 0);  /* 128 => no change */
  }
else error_moan(10, "string");
}

/*** Playtranspose ***/

static void 
p_playtranspose(void)
{
int transpose;
if (!read_expect_integer(&transpose, FALSE, TRUE)) return;
makechange(128, 128, 128, 128, transpose);
}

/*** Playvolume ***/

static void 
p_playvolume(void)
{
int volume;
if (!read_expect_integer(&volume, FALSE, FALSE)) return;
if (volume > 15)
  {
  error_moan(10, "Number between 0 and 15");
  return;
  }
makechange(128, 128, 128, volume, 0);
}


/************************************************************************
************************************************************************/


/*************************************************
*                Move & Rmove                    *
*************************************************/

static void 
p_move(void)
{
int x;
int y = 0;
b_movestr *p;

if (!read_expect_integer(&x, TRUE, TRUE)) return;
sigch();
if (read_ch == ',')
  {
  next_ch();
  if (!read_expect_integer(&y, TRUE, TRUE)) return;
  }

p = store_getitem(b_move);
p->x = x;
p->y = y;
p->relative = read_dir->arg1;
}


/*************************************************
*                 Name                           *
*************************************************/

/* The stave magnification is used only if an explicit size is given; otherwise
the fixed size is used. */

static void 
p_name(void)
{
sigch();

/* Handle [name <n>] */

if (isdigit(read_ch))
  {
  b_namestr *p = store_getitem(b_name);
  p->n = read_integer(FALSE);
  return;
  }

/* Else handle any number of <string> <draw> pairs; either or both may be
present in each case. */

for (;;)
  {
  uschar *ss;
  snamestr *p;
  snamestr **pp;
  int size = ff_offset_init;

  sigch();
  if (read_ch != '\"' && (read_ch != 'd' || Ustrncmp(read_chptr, "raw ", 4 != 0)))
    break;

  p = store_Xget(sizeof(snamestr));
  pp = &(stavehead->stave_name);
  while (*pp != NULL) pp = &((*pp)->next);

  p->next = NULL;
  p->text = NULL;
  p->drawing = NULL;
  p->flags = 0;

  /* Handle a text string */

  if (read_ch == '\"')
    {
    p->text = ss = string_read();
    string_check(ss);

    p->linecount = 1;
    while (*ss) if (*ss++ == '|') p->linecount += 1;

    while (read_ch == '/')
      {
      next_ch();
      if (read_ch == 'c')
        {
        p->flags |= snf_hcentre;
        next_ch();
        }
      else if (read_ch == 'm')
        {
        p->flags |= snf_vcentre;
        next_ch();
        }
      else if (read_ch == 'e')
        {
        p->flags |= snf_rightjust;
        next_ch();
        }
      else if (read_ch == 's')
        {
        next_ch();
        if (read_expect_integer(&size, FALSE, FALSE))
          {
          if (--size < 0 || size >= MaxTextFont)
            error_moan(39, MaxTextFont);
          }
        else return;
        }
      else if (read_ch == 'v')
        {
        p->flags |= snf_vertical;
        next_ch();
        }
      else
        {
        error_moan(10, "/c, /e, /m, /s or /v");
        return;
        }
      }

    p->offset = size;
    }

  /* Handle a drawing; might follow a string, so check again */

  sigch();
  if (read_ch == 'd' && Ustrncmp(read_chptr, "raw ", 4) == 0)
    {
    uschar word[80];
    drawitem args[20];
    drawitem *drawargs = NULL;
    int argcount = 0;
    tree_node *node;

    read_chptr += 4;
    read_ch = ' ';
    sigch();

    while (isdigit(read_ch) || 
           read_ch == '-'   || 
           read_ch == '+'   || 
           read_ch == '\"')
      {
      if (read_ch == '\"') 
        {
        args[++argcount].d.ptr = read_draw_text();
        args[argcount].dtype = dd_text; 
        } 
      else 
        {
        if (!read_expect_integer(&(args[++argcount].d.val), TRUE, TRUE)) break;
        args[argcount].dtype = dd_number; 
        } 
      sigch();
      }

    if (argcount > 0)
      {
      int i;
      drawargs = store_Xget((argcount+1)*sizeof(drawitem));
      drawargs[0].dtype = dd_number; 
      drawargs[0].d.val = argcount;
      for (i = 1; i <= argcount; i++) drawargs[i] = args[i];
      }

    read_word(word);
    node = Tree_Search(draw_tree, word);
    if (node == NULL) error_moan(70, word);
    p->drawing = node;
    p->args = drawargs;
    }

  /* Set up to move on to another one */

  *pp = p;
  pp = &(p->next);
  }
}


/*************************************************
*                 Newmovement                    *
*************************************************/

/* [newmovement] is unexpected here - give a tidy error message */

static void 
p_newmovement(void)
{
error_moan(73);  /* this stops processing */
}


/*************************************************
*                 Nocheck                        *
*************************************************/

static void 
p_nocheck(void)
{
stave_checklength = FALSE;
}


/*************************************************
*                Nocount                         *
*************************************************/

/* Set up the bar number vector now in case of an error in this bar. However,
ignore if two nocounts in the same bar (causes big trouble and can happen if a
bar line is accidentally omitted). */

static void 
p_nocount(void)
{
if (!stave_hadnocount)
  {
  if (++stave_totalnocount > (curmovt->barnovector)[stave_barnumber+1])
    (curmovt->barnovector)[stave_barnumber+1] = stave_totalnocount;
  stave_hadnocount = TRUE;
  }    
}


/*************************************************
*                 Noteheads                      *
*************************************************/

static void 
p_noteheads(void)
{
int nh;
uschar word[80];
b_noteheadsstr *p;

read_word(word);
stave_stemflag = nf_stem;

if (Ustrcmp(word, "only") == 0)
  {
  stave_stemflag = 0;
  nh = nh_only;
  }
else if (Ustrcmp(word, "direct") == 0)
  {
  stave_stemflag = 0;
  nh = nh_direct;
  }
else if (Ustrcmp(word, "normal") == 0) nh = nh_normal;
else if (Ustrcmp(word, "harmonic") == 0) nh = nh_harmonic;
else if (Ustrcmp(word, "cross") == 0) nh = nh_cross;
else if (Ustrcmp(word, "none") == 0) nh = nh_none;
else
  {
  error_moan(10, "\"normal\", \"harmonic\", \"cross\", \"none\", \"only\", or \"direct\"");
  return;
  }

p = store_getitem(b_noteheads);
p->value = nh;
}



/*************************************************
*                 Notes                          *
*************************************************/

static void 
p_notes(void)
{
int flag = FALSE;
b_charvaluestr *p = store_getitem(b_notes);
uschar word[80];
read_word(word);
if (Ustrcmp(word, "on") == 0) flag = TRUE;
  else if (Ustrcmp(word, "off") != 0) error_moan(10, "\"on\" or \"off\"");
stave_notes = p->value = flag;
}


/*************************************************
*              Notespacing                       *
*************************************************/

/* There are three possible formats */

static void 
p_ns(void)
{
sigch();
if (read_ch == '*')            /* Multiplicative */
  {
  int f;
  b_nsmstr *p;
  next_ch();
  if (!read_expect_integer(&f, TRUE, FALSE)) return;
  if (read_ch == '/')
    {
    int d;
    next_ch();
    if (!read_expect_integer(&d, TRUE, FALSE)) return;
    f = mac_fdiv(f, d);
    }
  p = store_getitem(b_nsm);
  p->value = f;
  }
else if (isdigit(read_ch) || read_ch == '+' || read_ch == '-')
  {                            /* Individual additive */
  int i, x;
  b_nsstr *p = store_getitem(b_ns);
  for (i = 0; i < 8; i++) p->ns[i] = 0;
  for (i = 0; i < 8; i++)
    {
    sigch();
    if (!isdigit(read_ch) && read_ch != '+' && read_ch != '-') break;
    if (!read_expect_integer(&x, TRUE, TRUE)) break;
    p->ns[i] = x;
    if (read_ch == ',') next_ch();
    }
  if (i == 1) error_moan(89);  /* Single change only may be a typo: warn */
  }
else store_getitem(b_ens);     /* Reset */
}


/*************************************************
*                 Octave                         *
*************************************************/

static void 
p_octave(void)
{
int x;
if (!read_expect_integer(&x, FALSE, TRUE)) return;
stave_octave = 12*x;
stave_lastbasenoteptr = NULL;
}


/*************************************************
*                Omitempty                       *
*************************************************/

static void 
p_omitempty(void)
{
stavehead->omitempty = TRUE;
}


/*************************************************
*                  Page                          *
*************************************************/

static void 
p_page(void)
{
b_pagestr *p = store_getitem(b_page);
sigch();
if (read_ch == '+')
  {
  p->relative = read_ch;
  next_ch();
  }
else p->relative = 0;
read_expect_integer(&(p->value), FALSE, FALSE);
}


/*************************************************
*                Percussion                      *
*************************************************/

static void 
p_percussion(void)
{
stavehead->stavelines = 128 + 1;    /* 128 => no clefs or keys */
}


/*************************************************
*               Printpitch                       *
*************************************************/

static void 
p_printpitch(void)
{
sigch();
if (read_ch == '*')
  {
  stave_printpitch = 0;
  next_ch();
  }
else stave_printpitch = read_stavepitch();
}


/*************************************************
*                  Reset                         *
*************************************************/

static void 
p_reset(void)
{
(void)store_getitem(b_reset);

if (stave_beaming) read_setbeamstems();

if (stave_barlength > stave_maxbarlength) stave_maxbarlength = stave_barlength;

if (!stave_resetOK)
  error_moan((stave_barlength == 0)? 67 : 34);
    else if (stave_pletlen) error_moan(35);

/* We do the action anyway, to prevent spurious over-long line errors */

read_initbaraccs(baraccs, stave_key);
stave_barlength = 0;

stave_resetOK = FALSE;
}


/*************************************************
*                  Resume                        *
*************************************************/

static void 
p_resume(void)
{
(void)store_getitem(b_resume);
stave_suspended = FALSE;
}


/*************************************************
*                Rlevel                          *
*************************************************/

static void 
p_rlevel(void)
{
(void)read_expect_integer(&stave_restlevel, TRUE, TRUE);
if (opt_oldrestlevel) stave_restlevel *= 2;
}


/*************************************************
*              Rspace & Space                    *
*************************************************/

static void 
p_rspace(void)
{
int x;
b_spacestr *p;
if (!read_expect_integer(&x, TRUE, TRUE)) return;
p = store_getitem(b_space);
p->value = x;
p->relative = read_dir->arg1;
}


/*************************************************
*                  Skip                          *
*************************************************/

static void 
p_skip(void)
{
int x;
if (!read_expect_integer(&x, FALSE, FALSE)) return;

/* Abandon this bar if there is nothing in it, else terminate */

if ((stavehead->barindex)[stave_barnumber] == store_nextitem())
  (stavehead->barindex)[stave_barnumber] = NULL;
else 
  {
  b_Endstr *b = store_getitem(b_End);
  b->overbeam = FALSE;
  b->barlinestyle = stave_barlinestyle; 
  } 

/* Advance to the required bar */

stave_barnumber += x;
(stavehead->barindex)[stave_barnumber] = store_nextitem();
}


/*************************************************
*              Slur and line                     *
*************************************************/

/* The basic slur structure is quite small; separate structures are used for
sets of modifications. They are chained together for convenience, and a
slurmod structure is created when necessary. The sequence number 0 means
"the unsplit slur" while other counts are for parts of a split slur. For
backwards compatiblity, we retain the following synonyms:

  sly = 1ry
  sry = 2ly
  slc = 1c
  src = 2c

A local subroutine is used to find the relevant slurmod on the chain, or to
create a new one if it isn't found. 

Arguments:
  sequence     the sequence number
  anchor       points to the anchor of the chain
  
Returns:       pointer to the required slurmod  
*/

static b_slurmodstr *
findmods(int sequence, b_slurmodstr **anchor)
{
b_slurmodstr *m = *anchor;
while (m != NULL)
  {
  if (m->sequence == sequence) return m;
  m = m->next;
  }
m = store_getitem(b_slurmod);
memset(m, 0, sizeof(b_slurmodstr));
m->type = b_slurmod;
m->next = *anchor;
*anchor = m;
m->sequence = sequence;
return m;
}

/*** Slur ***/

static void 
p_slur(void)
{
b_slurstr *p;
BOOL control_point_moved = FALSE;
int slurid = 0;
int flags = read_dir->arg1;
int ally = 0;
b_slurmodstr *modchain = NULL;
b_slurmodstr *mods = NULL;
sigch();

/* Loop to read the many options. */

while (read_ch == '/')
  {
  int *a, *b, x;
  next_sigch();

  /* Some things may appear only before the first split number qualifier. */

  if (mods != NULL && mods->sequence != 0)
    {
    if (strchr("=abeshiow", read_ch) != NULL) error_moan(113, read_ch);
    }

  switch (read_ch)
    {
    case '=':
    next_ch(); slurid = read_ch; next_ch();
    break;

    case 'a':
    flags &= ~(sflag_b | sflag_abs | sflag_lay);
    next_ch();
    if (read_ch == 'o')
      {
      next_ch();
      flags |= sflag_lay;
      }
    else if (isdigit(read_ch) || read_ch == '-')
      {
      if (!read_expect_integer(&x, TRUE, TRUE)) return;
      flags |= sflag_abs;
      ally += x;
      }
    break;

    case 'b':
    flags &= ~(sflag_abs | sflag_lay);
    flags |= sflag_b;
    next_ch();
    if (read_ch == 'u')
      {
      next_ch();
      flags |= sflag_lay;
      }
    else if (isdigit(read_ch) || read_ch == '-')
      {
      if (!read_expect_integer(&x, TRUE, TRUE)) return;
      flags |= sflag_abs;
      ally -= x;
      }
    break;

    case 'c':
    next_ch();
    if (mods == NULL) mods = findmods(0, &modchain);
    if (read_ch == 'i' || read_ch == 'o')
      {
      int s = read_ch == 'o'? +1 : -1;
      next_ch();
      if (!read_expect_integer(&x, TRUE, FALSE)) return;
      mods->c += x*s;
      }
    else if (read_ch == 'l' || read_ch == 'r')
      {
      BOOL left = read_ch == 'l';
      control_point_moved = TRUE; 
      next_ch();
      if (read_ch == 'u' || read_ch == 'd' || read_ch == 'l' || read_ch == 'r')
        {
        int cc = read_ch;
        next_ch();
        if (!read_expect_integer(&x, TRUE, FALSE)) return;
        switch(cc)
          {
          case 'u':
          if (left) mods->cly += x; else mods->cry += x;
          break;

          case 'd':
          if (left) mods->cly -= x; else mods->cry -= x;
          break;

          case 'l':
          if (left) mods->clx -= x; else mods->crx -= x;
          break;

          case 'r':
          if (left) mods->clx += x; else mods->crx += x;
          break;
          }
        }
      else error_moan(10, "clu, cld, cll, clr, cru, crd, crl, or crr");
      }
    else error_moan(10, "ci, co, clu, cld, cll, clr, cru, crd, crl, or crr");
    break;

    case 'u':
    next_ch();
    if (!read_expect_integer(&x, TRUE, FALSE)) return;
    if (mods == NULL || mods->sequence == 0) ally += x; else
      {
      mods->ly += x;
      mods->ry += x;
      } 
    break;

    case 'd':
    next_ch();
    if (!read_expect_integer(&x, TRUE, FALSE)) return;
    if (mods == NULL || mods->sequence == 0) ally -= x; else
      {
      mods->ly -= x;
      mods->ry -= x;
      } 
    break;

    case 'e':
    flags |= sflag_e;
    next_ch();
    break;

    case 'l':
    case 'r':
    if (mods == NULL) mods = findmods(0, &modchain); 
    a = (read_ch == 'l')? &(mods->ly) : &(mods->ry);
    b = (read_ch == 'l')? &(mods->lx) : &(mods->rx);
    next_ch();
    if (read_ch != 'u' && read_ch != 'd' && read_ch != 'l' && read_ch != 'r')
      error_moan(10, "lu, ld, ll, lr, ru, rd, rl, or rr");
    else
      {
      int  s = (read_ch == 'u' || read_ch == 'r')? +1 : -1;
      int *z = (read_ch == 'l' || read_ch == 'r')? b : a;
      next_ch();
      if (!read_expect_integer(&x, TRUE, FALSE)) return;
      *z += x*s;
      }
    break;

    /* The s... options are obsolete, referring to the first splitting point
    in a way that was limited and confusing. Keep them for compatibility,
    though. */

    case 's':
    next_ch();
    if (read_ch == 'l' || read_ch == 'r')
      {
      int s = 0;
      int *z = NULL;
      b_slurmodstr *tempmods;
       
      if (read_ch == 'l') 
        { 
        tempmods = findmods(1, &modchain); 
        a = &(tempmods->ry);
        b = &(tempmods->c); 
        }
      else 
        { 
        tempmods = findmods(2, &modchain);
        a = &(tempmods->ly);
        b = &(tempmods->c);   
        }
         
      next_ch();
      if (read_ch == 'u' || read_ch == 'd')
        {
        s = (read_ch == 'u')? +1 : -1;
        z = a;
        }
      else if (read_ch == 'c')
        {
        next_ch();
        if (read_ch != 'i' && read_ch != 'o')
          error_moan(10, "slci or slco");
        else
          {
          s = (read_ch == 'o')? +1 : -1;
          z = b;
          }
        }
      else error_moan(10, "u, d, ci or co");

      if (z != NULL)
        {
        next_ch();
        if (!read_expect_integer(&x, TRUE, FALSE)) return;
        *z += s*x;
        }
      }
    else error_moan(10, "sl.. or sr..");
    break;

    case 'h':
    flags |= sflag_h;
    next_ch();
    break;

    case 'i':
    flags |= sflag_i;
    next_ch();
    if (read_ch == 'p')
      {  
      flags |= sflag_idot;
      next_ch();
      } 
    break;

    case 'o':
    next_ch();
    if (read_ch == 'l') flags |= sflag_ol;
      else if (read_ch == 'r') flags |= sflag_or;
        else error_moan(10, "ol or or");
    next_ch();
    break;

    case 'w':
    flags |= sflag_w;
    next_ch();
    break;

    default:
    if (isdigit(read_ch))
      {
      int n = read_integer(FALSE);
      if (n == 0) error_moan(37, "number greater than zero");
      mods = findmods(n, &modchain);
      sigch();
      }
    else error_moan(10, "=, a, b, w, ci, co, d, e, u, lu, ld, ru, rd, h, i, ol, or, or number");
    break;
    }

  sigch();
  }

/* We don't allow wiggly with line slurs */

if ((flags & sflag_w) != 0)
  {
  if ((flags & sflag_l) != 0) error_moan(33, "lines");
  }
  
/* We don't support editorial marks on dotted or dashed slurs */

/* ... but they have been requested, even though they may end up
drawing the editorial mark through a space ...

if ((flags & sflag_e) != 0)
  {
  if ((flags & sflag_i) != 0) error_moan(94);
  }   
*/   

/* Now output the slur proper, and count for nesting check. */

p = store_getitem(b_slur);
p->flags = flags;
p->id = slurid;
p->ally = ally;
p->mods = modchain;
stave_slurcount++;
}


/*************************************************
*                  Smove                         *
*************************************************/

static void 
p_smove(void)
{
b_movestr *p;
if (!read_expect_integer(&stave_smove, TRUE, TRUE)) return;
p = store_getitem(b_move);
p->x = stave_smove;
p->y = 0;
p->relative = stave_smove_relative = read_dir->arg1;
}


/*************************************************
*               Stavelines                       *
*************************************************/

static void 
p_stavelines(void)
{
int n;
if (!read_expect_integer(&n, FALSE, FALSE)) return;
if (n > 6) error_moan(10, "Number in the range 0-6");
  else stavehead->stavelines = n;
}


/*************************************************
*                Stavespacing                    *
*************************************************/

static void 
p_ss(void)
{
unsigned int done[stave_bitvec_size];

sigch();
mac_initstave(done, 0);
for (;;)
  {
  int spacing, stave;
  int opt = (read_ch == '+' || read_ch == '-')? '+' : ' ';

  if (!read_expect_integer(&spacing, TRUE, TRUE)) return;

  if (read_ch != '/') stave = curstave; else
    {
    if (opt != ' ' || spacing < 0 || (spacing%1000) != 0)
      {
      error_moan(10, "Stave number");
      return;
      }
    stave = spacing/1000;
    next_ch();
    opt = (read_ch == '+' || read_ch == '-')? '+' : ' ';
    if (!read_expect_integer(&spacing, TRUE, TRUE)) return;
    }

  if (stave > max_stave) error_moan(42, max_stave); else
    {
    b_ssstr *p = store_getitem(read_dir->arg1);
    p->opt = opt;
    p->stave = stave;
    p->value = spacing;
    }
    
  if (mac_teststave(done, stave)) error_moan(106, stave, read_dir->name);
  mac_setstave(done, stave); 
  sigch();
  if (read_ch == ',') next_sigch();
  if (read_ch != '+' && read_ch != '-' && !isdigit(read_ch)) break;
  }
}


/*************************************************
*                 Stems/Ties                     *
*************************************************/

static void 
p_stems(void)
{
int *p = (read_dir->arg1 == 1)? &stave_stemforce : &stave_ties;
uschar word[80];
read_word(word);
if (Ustrcmp(word, "auto") == 0) *p = 0;
else if (Ustrcmp(word, "up") == 0 || Ustrcmp(word, "above") == 0) *p = +1;
else if (Ustrcmp(word, "down") == 0 || Ustrcmp(word, "below") == 0) *p = -1;
else error_moan(10, "\"auto\", \"above\", \"up\", \"below\", or \"down\"");
}


/*************************************************
*               Stemlength = Sl                  *
*************************************************/

static void 
p_stemlength(void)
{
(void)read_expect_integer(&stave_stemlength, TRUE, TRUE);
if (opt_oldstemlength) stave_stemlength *= 2;
}


/*************************************************
*                  Suspend                       *
*************************************************/

static void 
p_suspend(void)
{
(void)store_getitem(b_suspend);
stave_suspended = TRUE;
}


/*************************************************
*                Systemgap                       *
*************************************************/

static void 
p_sg(void)
{
b_sgstr *p;
int opt, value;

sigch();
opt = (read_ch == '+' || read_ch == '-')? '+' : ' ';
if (!read_expect_integer(&value, TRUE, TRUE)) return;

p = store_getitem(read_dir->arg1);
p->opt = opt;
p->value = value;
}


/*************************************************
*                  Text                          *
*************************************************/

static void 
p_text(void)
{
int sign = 1;
uschar word[80];
read_word(word);
stave_textabsolute = 0;

if (Ustrcmp(word, "underlay") == 0) stave_textflags = text_ul;
else if (Ustrcmp(word, "overlay") == 0) stave_textflags = text_ul | text_above;
else if (Ustrcmp(word, "fb") == 0) stave_textflags = text_fb;

else
  {
  if (Ustrcmp(word, "above") == 0) stave_textflags = text_above;
  else if (Ustrcmp(word, "below") == 0)
    {
    stave_textflags = 0;
    sign = -1;
    }
  else
    {
    error_moan(10, "\"underlay\", \"fb\", \"above\", or \"below\"");
    return;
    }

  /* Check for absolute setting */

  sigch();
  if (isdigit(read_ch))
    {
    stave_textflags |= text_absolute;
    stave_textabsolute = sign*read_integer(TRUE);
    }
  }
}


/*************************************************
*                 Time                           *
*************************************************/

static void 
p_time(void)
{
int warn = curmovt->timewarn;
int t = read_time();           /* returns 0 after giving error */
int tt = t;

if (t == 0) return;

/* If the time signature is followed by "->" then we read a second signature
to which bars are to be musically stretched or compressed. */

sigch();
if (read_ch == '-' && *read_chptr == '>')
  {
  read_chptr++;
  next_ch();
  tt = read_time();
  if (tt == 0) tt = t;
  }

/* Set up stretching numerator and denominator. So as not to waste time
multiplying in the common case, indicate that with numerator == 0. */

stave_requiredbarlength = read_compute_barlength(tt);

if (t == tt) stave_matchnum = 0; else
  {
  stave_matchnum = stave_requiredbarlength;
  stave_matchden = read_compute_barlength(t);
  }

/* Now test for "nowarn". */

read_word(read_stavedir);

if (Ustrcmp(read_stavedir, "nowarn") == 0)
  {
  read_stavedir[0] = 0;
  warn = FALSE;
  }

if (!read_assumeflag)
  {
  b_timestr *p = store_getitem(b_time);
  p->time = t;
  p->warn = warn;
  p->suppress = !curmovt->showtime;      /* Suppress if notime */
  }
else
  {
  b_settimestr *p = store_getitem(b_settime);
  p->value = t;
  read_assumeflag = FALSE;
  }
}


/*************************************************
*                 Transpose                      *
*************************************************/

/* A stave transpose does not of itself change the key signature. This is
a facility, not a bug! However, we must call the routine in order to set up the
letter count for transposing notes. The yield is discarded. */

static void 
p_transpose(void)
{
int x;
if (!read_expect_integer(&x, FALSE, TRUE)) return;
if (stave_transpose >= max_transpose) stave_transpose = 0;
stave_transpose += x;
(void)transpose_key(stave_key, stave_transpose, TRUE);
stave_lastbasenoteptr = NULL;
}


/*************************************************
*                Transposedacc                   *
*************************************************/

static void 
p_transposedacc(void)
{
uschar word[80];
read_word(word);
if (Ustrcmp(word, "force") == 0) stave_transposedaccforce = TRUE;
  else if (Ustrcmp(word, "noforce") == 0) stave_transposedaccforce = FALSE;
    else error_moan(10, "\"force\" or \"noforce\"");
}


/*************************************************
*                 Tremolo                        *
*************************************************/

static void 
p_tremolo(void)
{
b_tremolostr *p;
int count = 2;
int join = 0;

sigch();
while (read_ch == '/')
  {
  next_ch();
  if (read_ch == 'x' || read_ch == 'j')
    {
    int *xp = (read_ch == 'x')? &count : &join;
    next_ch();
    if (!read_expect_integer(xp, FALSE, FALSE)) return;
    }
  else error_moan(10, "\"x\" or \"j\"");
  sigch();
  }

(void)store_getitem(b_beambreak);
p = store_getitem(b_tremolo);
p->count = count;
p->join = join;
}


/*************************************************
*               Triplets                         *
*************************************************/

static void 
p_triplets(void)
{
int hadone = FALSE;
int flag = TRUE;
b_charvaluestr *p = store_getitem(b_tripsw);

for (;;)
  {
  read_word(read_stavedir);

  if (Ustrcmp(read_stavedir, "above") == 0)
    {
    stave_pletflags &= ~plet_b;
    stave_pletflags |=  plet_a;
    goto ADJUST;
    }
   else if (Ustrcmp(read_stavedir, "below") == 0)
    {
    stave_pletflags &= ~plet_a;
    stave_pletflags |=  plet_b;
ADJUST:
    stave_plety = 0;
    stave_pletflags &= ~plet_abs;
    sigch();
    if (read_ch == '+' || read_ch == '-')
      (void)read_expect_integer(&stave_plety, TRUE, TRUE);
    else if (isdigit(read_ch))
      {
      stave_pletflags |= plet_abs;
      stave_plety = read_integer(TRUE);
      if ((stave_pletflags & plet_b) != 0) stave_plety = -stave_plety;
      }
    }
  else if (Ustrcmp(read_stavedir, "auto") == 0)
    {
    stave_pletflags &= ~(plet_a | plet_b | plet_abs | plet_bn | plet_by);
    stave_plety = 0;
    }
  else if (Ustrcmp(read_stavedir, "bracket") == 0)
    {
    stave_pletflags &= ~plet_bn;
    stave_pletflags |=  plet_by;
    }
  else if (Ustrcmp(read_stavedir, "nobracket") == 0)
    {
    stave_pletflags &= ~plet_by;
    stave_pletflags |=  plet_bn;
    }
  else if (Ustrcmp(read_stavedir, "off") == 0) flag = FALSE;
  else if (Ustrcmp(read_stavedir, "on") == 0) flag = TRUE;
  else break;

  hadone = TRUE;
  }

if (!hadone)
  error_moan(10, "\"above\", \"below\", \"auto\", \"[no]bracket\", \"on\", or \"off\"");

p->value = flag;
}


/*************************************************
*                   Ulevel/Olevel                *
*************************************************/

/* Knows that ulevelstr has the same form as olevelstr; the actual type
required is in the argument. */

static void 
p_uolevel(void)
{
b_ulevelstr *p;
int autoflag, value;

sigch();
if (read_ch == '*')
  {
  autoflag = TRUE;
  next_ch();
  }
else
  {
  if (!read_expect_integer(&value, TRUE, TRUE)) return;
  autoflag = FALSE;
  }

p = store_getitem(read_dir->arg1);
p->opt = autoflag;
p->value = value;
}


/*************************************************
*           Table of stave directives            *
*************************************************/

static dirstr read_stavedirlist[] = {

  { "all",           p_common,     b_all,      TRUE },
  { "alto",          p_clef,       clef_alto, FALSE },
  { "assume",        p_assume,     0,          TRUE },
  { "baritone",      p_clef,       clef_baritone, FALSE },
  { "barlinestyle",  p_barlinestyle, 0,        TRUE },
  { "barnumber",     p_barnum,     0,          TRUE },
  { "bass",          p_clef,       clef_bass, FALSE },
  { "beamacc",       p_beamaccrit, b_beamacc, FALSE },
  { "beammove",      p_svalue,     b_offset,   TRUE },
  { "beamrit",       p_beamaccrit, b_beamrit, FALSE },
  { "beamslope",     p_svalue,     b_slope,    TRUE },
  { "bottommargin",  p_pvalue,     b_pagebots, TRUE },
  { "bowing",        p_above,      b_bowing,   TRUE },
  { "breakbarline",  p_common,     b_breakbarline, TRUE },
  { "cbaritone",     p_clef,       clef_cbaritone, FALSE },
  { "comma",         p_common,     b_comma,   FALSE },
  { "contrabass",    p_clef,       clef_contrabass, FALSE },
  { "copyzero",      p_svalue,     b_zcopy, TRUE },
  { "couple",        p_couple,     0, TRUE },
  { "cue",           p_cue,        0, TRUE },
  { "deepbass",      p_clef,       clef_deepbass, FALSE },
  { "dots",          p_dots,       0, TRUE },
  { "draw",          p_draw,       FALSE, FALSE },
  { "el",            p_endline,    0, TRUE },
  { "endcue",        p_endcue,     0, TRUE },
  { "endline",       p_endline,    0, TRUE },
  { "endslur",       p_endline,    0, TRUE },
  { "endstaff",      p_endstave,   0, TRUE },
  { "endstave",      p_endstave,   0, TRUE },
  { "ensure",        p_pvalue,     b_ensure, FALSE },
  { "es",            p_endline,    0, TRUE },
  { "fbfont",        p_font,       0, TRUE },
  { "fbtextsize",    p_size,       0, TRUE },
  { "footnote",      p_footnote,   0, TRUE },
  { "h",             p_nh,         nh_harmonic, TRUE },
  { "hairpins",      p_hairpins,   0, TRUE },
  { "hairpinwidth",  p_hairpinwidth, 0, TRUE },
  { "hclef",         p_clef,       clef_h, FALSE },
  { "justify",       p_justify,    0, TRUE },
  { "key",           p_key,        0, FALSE },
  { "line",          p_slur,       sflag_l, FALSE },
  { "linegap",       p_linegap,    FALSE, FALSE },
  { "mezzo",         p_clef,       clef_mezzo, FALSE },
  { "midichannel",   p_midichannel,0, TRUE },
  { "midipitch",     p_midipitch,  0, TRUE },
  { "miditranspose", p_playtranspose, 0, TRUE },
  { "midivoice",     p_midivoice,  0, TRUE },
  { "midivolume",    p_playvolume, 0, TRUE },
  { "move",          p_move,       FALSE, FALSE },
  { "name",          p_name,       0, TRUE },
  { "newline",       p_common,     b_newline, TRUE },
  { "newmovement",   p_newmovement,0, TRUE },
  { "newpage",       p_common,     b_newpage, TRUE },
  { "nocheck",       p_nocheck,    0, TRUE },
  { "noclef",        p_clef,       clef_none, FALSE },
  { "nocount",       p_nocount,    0, TRUE },
  { "noteheads",     p_noteheads,  0, TRUE },
  { "notes",         p_notes,      0, TRUE },
  { "notespacing",   p_ns,         0, TRUE },
  { "ns",            p_ns,         0, TRUE },
  { "o",             p_nh,         nh_normal, TRUE },
  { "octave",        p_octave,     0, TRUE },
  { "olevel",        p_uolevel,    b_olevel, TRUE },
  { "olhere",        p_svalue,     b_olhere, TRUE },
  { "oltextsize",    p_size,       3, TRUE },
  { "omitempty",     p_omitempty,  0, TRUE },
  { "overdraw",      p_draw,       TRUE, FALSE },
  { "overlayfont",   p_font,       3, TRUE },
  { "page",          p_page,       0, TRUE },
  { "percussion",    p_percussion, 0, TRUE },
  { "playtranspose", p_playtranspose, 0, TRUE },
  { "playvolume",    p_playvolume, 0, TRUE },
  { "printpitch",    p_printpitch, 0, TRUE },
  { "reset",         p_reset,      0, TRUE },
  { "resume",        p_resume,     0, TRUE },
  { "rlevel",        p_rlevel,     0, TRUE },
  { "rmove",         p_move,       TRUE, FALSE },
  { "rsmove",        p_smove,      TRUE, FALSE },
  { "rspace",        p_rspace,     TRUE, FALSE },
  { "sghere",        p_sg,         b_sghere, TRUE },
  { "sgnext",        p_sg,         b_sgnext, TRUE },
  { "skip",          p_skip,       0, TRUE },
  { "sl",            p_stemlength, 0, TRUE },
  { "slur",          p_slur,       0, FALSE },
  { "slurgap",       p_linegap,    TRUE, FALSE },
  { "smove",         p_smove,      FALSE, FALSE },
  { "soprabass",     p_clef,       clef_soprabass, FALSE },
  { "soprano",       p_clef,       clef_soprano, FALSE },
  { "space",         p_rspace,     FALSE, FALSE },
  { "sshere",        p_ss,         b_sshere, TRUE },
  { "ssnext",        p_ss,         b_ssnext, TRUE },
  { "stavelines",    p_stavelines, 0, TRUE },
  { "stemlength",    p_stemlength, 0, TRUE},
  { "stems",         p_stems,      1, TRUE },
  { "suspend",       p_suspend,    0, TRUE },
  { "tenor",         p_clef,       clef_tenor, FALSE },
  { "text",          p_text,       0, TRUE },
  { "textfont",      p_font,       1, TRUE },
  { "textsize",      p_size,       1, TRUE },
  { "tick",          p_common,     b_tick, FALSE },
  { "ties",          p_stems,      2, TRUE },
  { "time",          p_time,       0, FALSE },
  { "topmargin",     p_pvalue,     b_pagetops, TRUE },
  { "transpose",     p_transpose,  0, TRUE },
  { "transposedacc", p_transposedacc, 0, TRUE },
  { "treble",        p_clef,       clef_treble, FALSE },
  { "trebledescant", p_clef,       clef_trebledescant, FALSE },
  { "trebletenor",   p_clef,       clef_trebletenor, FALSE },
  { "trebletenorb",  p_clef,       clef_trebletenorB, FALSE },
  { "tremolo",       p_tremolo,    0, TRUE },
  { "triplets",      p_triplets,   0, TRUE },
  { "ulevel",        p_uolevel,    b_ulevel, TRUE },
  { "ulhere",        p_svalue,     b_ulhere, TRUE },
  { "ultextsize",    p_size,       2, TRUE },
  { "unbreakbarline",p_common,     b_unbreakbarline, TRUE },
  { "underlayfont",  p_font,       2, TRUE },
  { "x",             p_nh,         nh_cross, TRUE },
  { "xline",         p_slur,       sflag_x+sflag_l, FALSE },
  { "xslur",         p_slur,       sflag_x, FALSE },
  { "z",             p_nh,         nh_none, 0 }
};

static int read_stavedirsize = sizeof(read_stavedirlist)/sizeof(dirstr);


/*************************************************
*            Handle a stave directive            *
*************************************************/

/* The directive name is already in read_stavedir. If processing
this one reads one word ahead, leave that word in read_stavedir
for next time. 

Arguments:  none
Returns:    nothing
*/

void 
read_stavedirective(void)
{
dirstr *first = read_stavedirlist;
dirstr *last  = first + read_stavedirsize;

while (last > first)
  {
  int c;
  read_dir = first + (last-first)/2;
  c = Ustrcmp(read_stavedir, read_dir->name);
  if (c == 0)
    {
    read_stavedir[0] = 0;
    (read_dir->proc)();
    if (!read_dir->arg2) stave_resetOK = FALSE;
    return;
    }
  if (c > 0) first = read_dir + 1; else last = read_dir;
  }

error_moan(32, read_stavedir);
read_stavedir[0] = 0;
}


/*************************************************
*            Read a clef name                    *
*************************************************/

/* This function is used by the printkey heading directive. It is here so that 
it can use the table above for the supported clefs, thus avoiding a duplicate
list.

Argument: none
Returns:  the key
*/

int read_clef(void)
{
uschar word[256];
dirstr *first, *last;

read_word(word);
if (word[0] == 0) { error_moan(105); return 0; }

first = read_stavedirlist;
last  = first + read_stavedirsize;

while (last > first)
  {
  int c;
  dirstr *d = first + (last-first)/2;
  c = Ustrcmp(word, d->name);
  if (c == 0)
    {
    if (d->proc == p_clef) return d-> arg1;
    break; 
    }
  if (c > 0) first = d + 1; else last = d;
  }
  
error_moan(126, word);
return 0; 
}

/* End of read4.c */
