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

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

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


/* 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);
next_word();
if (Ustrcmp(read_word, "above") == 0) flag = TRUE;
  else if (Ustrcmp(read_word, "below") != 0)
    error_moan(ERR10, "\"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(ERR10, "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 >= MaxFontSizes)
  { error_moan(ERR39, MaxFontSizes); 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(ERR10, "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 const 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)
{
usint i;
next_word();
for (i = 0; i < (sizeof(assume_list)/sizeof(uschar *)); i++)
  if (Ustrcmp(read_word, assume_list[i]) == 0)
    {
    read_assumeflag = TRUE;
    read_stavedir = TRUE;
    return;
    }
error_moan(ERR10, "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(ERR10, "/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))
  {
  next_word();
  if (Ustrcmp(read_word, "off") == 0) flag = FALSE;
    else read_stavedir = TRUE;
  }

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


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

static void
p_couple(void)
{
next_word();
if (Ustrcmp(read_word, "up") == 0) stave_couplestate = +1;
else if (Ustrcmp(read_word, "down") == 0) stave_couplestate = -1;
else if (Ustrcmp(read_word, "off") == 0) stave_couplestate = 0;
else error_moan(ERR10, "\"up\", \"down\", or \"off\"");
}


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

static void
p_cue(void)
{
stave_noteflags &= ~nf_cuedotalign;
stave_noteflags |= nf_cuesize;
sigch();
if (read_ch == '/')
  {
  next_ch();
  next_word();
  if (Ustrcmp(read_word, "dotalign") == 0) stave_noteflags |= nf_cuedotalign;
    else error_moan(ERR10, "\"dotalign\"");
  }
}


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

static void
p_dots(void)
{
next_word();
if (Ustrcmp(read_word, "above") == 0) stave_noteflags &= ~nf_lowdot;
else if (Ustrcmp(read_word, "below") == 0) stave_noteflags |= nf_lowdot;
else error_moan(ERR10, "\"above\" or \"below\"");
}


/*************************************************
*              Doublenotes                       *
*************************************************/

static void
p_doublenotes(void)
{
stave_notenum *= 2;
}


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

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

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

next_word();
node = Tree_Search(draw_tree, read_word);
if (node == NULL) error_moan(ERR70, read_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(ERR10, "\"=\""); 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(ERR17, "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)
{
stave_hairpinflags = stave_hairpiny = 0;
next_word();
if (Ustrcmp(read_word, "below") == 0) stave_hairpinflags = hp_below;
  else if (Ustrcmp(read_word, "middle") == 0)
    stave_hairpinflags = hp_below | hp_middle;
      else if (Ustrcmp(read_word, "above") != 0)
        {
        error_moan(ERR10, "\"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);
}


/*************************************************
*              Halvenotes                        *
*************************************************/

static void
p_halvenotes(void)
{
if (stave_notenum > 1) stave_notenum /= 2; else stave_noteden *= 2;
}



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

static void
p_key(void)
{
int warn = curmovt->keywarn;
int oldkey = stave_key_tp;
int oldwidth, newwidth;

stave_key = read_key();
read_initbaraccs(baraccs, stave_key);

stave_key_tp = transpose_key(stave_key, stave_transpose);
read_initbaraccs(baraccs_tp, stave_key_tp);

sigch();
if (isalpha(read_ch))
  {
  next_word();
  if (Ustrcmp(read_word, "nowarn") == 0) warn = FALSE;
    else read_stavedir = TRUE;
  }

oldwidth = misc_keywidth(oldkey | 64, stave_clef);
newwidth = misc_keywidth(stave_key_tp, stave_clef);

/* If both the old (cancellation) width and the new key width are zero, there's
nothing we can do about warning. */

if (oldwidth == 0 && newwidth == 0) warn = FALSE;

/* If not "assume" create a new key item, preceded by a cancellation key item
if the new signature is empty and the old one is not. */

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

  if (newwidth == 0 && oldwidth != 0)
    {
    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;
  }

/* "Assume": insert a change of key without any printing. */

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


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

static void
p_justify(void)
{
sigch();
if (read_ch == '+' || read_ch == '-')
  {
  while (read_ch == '+' || read_ch == '-')
    {
    b_justifystr *p;
    int opt = read_ch;
    int side;
    next_ch();
    next_word();
    sigch();
    if (Ustrcmp(read_word, "top") == 0) side = just_top;
    else if (Ustrcmp(read_word, "bottom") == 0) side = just_bottom;
    else if (Ustrcmp(read_word, "left") == 0) side = just_left;
    else if (Ustrcmp(read_word, "right") == 0) side = just_right;
    else
      {
      error_moan(ERR10, "\"top\", \"bottom\", \"left\", or \"right\"");
      return;
      }
    p = store_getitem(b_justify);
    p->opt = opt;
    p->side = side;
    }
  }
else error_moan(ERR10, "\"+\" 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];
      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];
        }
      next_word();
      draw = Tree_Search(draw_tree, read_word);
      if (draw == NULL) error_moan(ERR70, read_word);
      }
    else error_moan(ERR10, "\"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(ERR10, "/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 >= MaxFontSizes)
          { error_moan(ERR39, MaxFontSizes); 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(ERR10, "/box, /ring, or /s");
        break;
        }
      }
    break;

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

  sigch();
  }

if (stave_slurcount <= 0)
  error_moan(ERR17, "%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;
if (!read_expect_integer(&channel, FALSE, FALSE)) return;
if (channel < 1 || channel > MIDI_MAXCHANNEL)
  {
  error_moan(ERR109, MIDI_MAXCHANNEL);
  return;
  }

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

  if (read_ch == '/')
    {
    int vol;
    next_ch();
    if (read_expect_integer(&vol, FALSE, FALSE))
      {
      if (vol > 15)
        error_moan(ERR10, "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;
if (read_plainstring())
  {
  if (read_word[0] == 0) voicenumber = 129; else  /* => no change */
    {
    if (read_word[0] == '#') voicenumber = Uatoi(read_word+1);
      else voicenumber = read_getmidinumber(midi_voicenames, read_word, US"voice");
    if (voicenumber < 1 || voicenumber > 128)
      {
      error_moan(ERR109, "voice", 128);
      voicenumber = 1;
      }
    }
  makechange(128, voicenumber - 1, 128, 128, 0);  /* 128 => no change */
  }
else error_moan(ERR10, "string");
}

/*** Midipitch ***/

static void
p_midipitch(void)
{
int note;
if (read_plainstring())
  {
  if (read_word[0] == 0) note = 0;  /* => no more forcing */
    else if (read_word[0] == '#') note = Uatoi(read_word+1);
      else note = read_getmidinumber(midi_percnames, read_word, US"percussion instrument");
  makechange(128, 128, note, 128, 0);  /* 128 => no change */
  }
else error_moan(ERR10, "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(ERR10, "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)
{
snamestr **pp, **savepp;
BOOL reading_extra_strings = FALSE;

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. Add onto any existing chain. This code is a mess because
it was hacked to allow for drawings and then extra strings. */

pp = &(stavehead->stave_name);
while (*pp != NULL) pp = &((*pp)->next);

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

  /* Check for end of strings and drawings */

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

  /* Set up a stave name structure, either for a string or a drawing. Restart
  here for extra strings that are associated with a "main" string. */

  RESTART_STRING:
  size = ff_offset_init;
  p = store_Xget(sizeof(snamestr));
  *pp = p;
  pp = &(p->next);

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

  /* Read a string with possible extra strings attached. */

  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 >= MaxFontSizes)
            error_moan(ERR39, MaxFontSizes);
          }
        else return;
        }
      else if (read_ch == 'v')
        {
        p->flags |= snf_vertical;
        next_ch();
        }

      /* One or more additional strings may hang off the extra field; this
      allows for different options. Such strings must follow immediately. */

      else if (read_ch == '\"')
        {
        if (!reading_extra_strings)
          {
          savepp = pp;
          reading_extra_strings = TRUE;
          }

        p->offset = size;
        pp = &(p->extra);
        goto RESTART_STRING;
        }

      else
        {
        error_moan(ERR10, "/c, /e, /m, /s or /v");
        return;
        }
      }   /* End of '/' loop */

    p->offset = size;
    if (reading_extra_strings)
      {
      pp = savepp;
      reading_extra_strings = FALSE;
      }
    }

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

  sigch();
  if (read_ch == 'd' && Ustrncmp(read_chptr, "raw ", 4) == 0)
    {
    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];
      }

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


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

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

static void
p_newmovement(void)
{
error_moan(ERR73);  /* 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;
b_noteheadsstr *p;

next_word();
stave_stemflag = nf_stem;

if (Ustrcmp(read_word, "only") == 0)
  {
  stave_stemflag = 0;
  nh = nh_only;
  }
else if (Ustrcmp(read_word, "direct") == 0)
  {
  stave_stemflag = 0;
  nh = nh_direct;
  }
else if (Ustrcmp(read_word, "normal") == 0) nh = nh_normal;
else if (Ustrcmp(read_word, "harmonic") == 0) nh = nh_harmonic;
else if (Ustrcmp(read_word, "cross") == 0) nh = nh_cross;
else if (Ustrcmp(read_word, "none") == 0) nh = nh_none;
else
  {
  error_moan(ERR10, "\"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);
next_word();
if (Ustrcmp(read_word, "on") == 0) flag = TRUE;
  else if (Ustrcmp(read_word, "off") != 0) error_moan(ERR10, "\"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(ERR89);  /* 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)? ERR67 : ERR34);
    else if (stave_pletlen) error_moan(ERR35);

/* 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;
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(ERR113, 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 == 'x')
      {
      next_ch();
      flags |= sflag_cx;
      }
    else 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';
      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(ERR10, "clu, cld, cll, clr, cru, crd, crl, or crr");
      }
    else error_moan(ERR10, "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);

    x = (read_ch == 'l')? 0 : 1;
    next_ch();

    switch(read_ch)
      {
      case 'l':
      break;

      case 'r':
      x |= 2;
      break;

      case 'd':
      x |= 4;
      break;

      case 'u':
      x |= 6;
      break;

      default:
      x = -1;
      break;
      }

    next_ch();
    if (read_ch == 'c')
      {
      next_ch();
      if ((x & 4) == 0) x |= 8; else x = -1;
      }

    if (x < 0)
      {
      error_moan(ERR10, "lu, ld, ll, llc, lr, lrc, ru, rd, rl, rlc, rr, or rrc");
      }
    else
      {
      int s;
      int *z = NULL;  /* Stop compiler unset warning */

      switch (x)   /* 12-15 won't occur because c is only with left/right */
        {
        case 0:  /* ll */
        case 2:  /* lr */
        z = &(mods->lx);
        x -= 1;  /* -1 or +1 */
        break;

        case 1:  /* rl */
        case 3:  /* rr */
        z = &(mods->rx);
        x -= 2;  /* -1 or +1 */
        break;

        case 4:  /* ld */
        case 6:  /* lu */
        z = &(mods->ly);
        x -= 5;  /* -1 or +1 */
        break;

        case 5:  /* rd */
        case 7:  /* ru */
        z = &(mods->ry);
        x -= 6;  /* -1 or +1 */
        break;

        case 8:  /* llc */
        case 10: /* lrc */
        z = &(mods->lxoffset);
        x -= 9;  /* -1 or +1 */
        break;

        case 9:  /* rlc */
        case 11: /* rrc */
        z = &(mods->rxoffset);
        x -= 10; /* -1 or +1 */
        break;
        }

      if (!read_expect_integer(&s, 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(ERR10, "slci or slco");
        else
          {
          s = (read_ch == 'o')? +1 : -1;
          z = b;
          }
        }
      else error_moan(ERR10, "u, d, ci or co");

      if (z != NULL)
        {
        next_ch();
        if (!read_expect_integer(&x, TRUE, FALSE)) return;
        *z += s*x;
        }
      }
    else error_moan(ERR10, "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(ERR10, "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(ERR37, "number greater than zero");
      mods = findmods(n, &modchain);
      sigch();
      }
    else error_moan(ERR10, "=, 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(ERR33, "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(ERR94);
  }
*/

/* 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(ERR10, "Number in the range 0-6");
  else stavehead->stavelines = n;
}


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

static void
p_ss(void)
{
usint 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(ERR10, "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(ERR42, 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(ERR106, 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;
next_word();
if (Ustrcmp(read_word, "auto") == 0) *p = 0;
else if (Ustrcmp(read_word, "up") == 0 || Ustrcmp(read_word, "above") == 0) *p = +1;
else if (Ustrcmp(read_word, "down") == 0 || Ustrcmp(read_word, "below") == 0) *p = -1;
else error_moan(ERR10, "\"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;

next_word();
stave_textabsolute = 0;

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

else
  {
  if (Ustrcmp(read_word, "above") == 0) stave_textflags = text_above;
  else if (Ustrcmp(read_word, "below") == 0)
    {
    stave_textflags = 0;
    sign = -1;
    }
  else
    {
    error_moan(ERR10, "\"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". */

sigch();
if (isalpha(read_ch))
  {
  next_word();
  if (Ustrcmp(read_word, "nowarn") == 0) warn = FALSE;
    else read_stavedir = TRUE;
  }

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 == no_transpose) stave_transpose = 0;
stave_transpose += x;
if (abs(stave_transpose) > max_transpose)
  error_moan(ERR139, (stave_transpose == x)? "T":"Accumulated t", stave_transpose,
    max_transpose);  /* Hard error */
(void)transpose_key(stave_key, stave_transpose);
stave_lastbasenoteptr = NULL;
}


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

static void
p_transposedacc(void)
{
next_word();
if (Ustrcmp(read_word, "force") == 0) stave_transposedaccforce = TRUE;
  else if (Ustrcmp(read_word, "noforce") == 0) stave_transposedaccforce = FALSE;
    else error_moan(ERR10, "\"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(ERR10, "\"x\" or \"j\"");
  sigch();
  }

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


/*************************************************
*               Tripletize                       *
*************************************************/

static void
p_tripletize(void)
{
sigch();
if (isalpha(read_ch)) next_word();
  else Ustrcpy(read_word, "on");
if (Ustrcmp(read_word, "off") == 0)
  {
  stave_noteflags &= ~nf_tripletize;
  }
else
  {
  if (Ustrcmp(read_word, "on") != 0) read_stavedir = TRUE;
  stave_noteflags |= nf_tripletize;
  stave_tripletize = TRUE;   /* Check bar for tripletizing */
  }
}


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

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

for (;;)
  {
  next_word();

  if (Ustrcmp(read_word, "above") == 0)
    {
    stave_pletflags &= ~plet_b;
    stave_pletflags |=  plet_a;
    goto ADJUST;
    }
   else if (Ustrcmp(read_word, "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_word, "auto") == 0)
    {
    stave_pletflags &= ~(plet_a | plet_b | plet_abs | plet_bn | plet_by);
    stave_plety = 0;
    }
  else if (Ustrcmp(read_word, "bracket") == 0)
    {
    stave_pletflags &= ~plet_bn;
    stave_pletflags |=  plet_by;
    }
  else if (Ustrcmp(read_word, "nobracket") == 0)
    {
    stave_pletflags &= ~plet_by;
    stave_pletflags |=  plet_bn;
    }
  else if (Ustrcmp(read_word, "off") == 0) flag = FALSE;
  else if (Ustrcmp(read_word, "on") == 0) flag = TRUE;
  else break;

  hadone = TRUE;
  }

if (!hadone)
  {
  read_stavedir = TRUE;
  error_moan(ERR10, "\"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;
int value = 0;

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 },
  { "doublenotes",   p_doublenotes,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 },
  { "halvenotes",    p_halvenotes, 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 },
  { "sgabove",       p_sg,         b_sgabove, TRUE },
  { "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 },
  { "ssabove",       p_ss,         b_ssabove, TRUE },
  { "sshere",        p_ss,         b_sshere, TRUE },
  { "ssnext",        p_ss,         b_ssnext, TRUE },
  { "stafflines",    p_stavelines, 0, 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 },
  { "tripletize",    p_tripletize, 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_word. If processing this one reads one
word ahead, it sets read_stavedir TRUE.

Arguments:  none
Returns:    nothing
*/

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

read_stavedir = FALSE;
while (last > first)
  {
  int c;
  read_dir = first + (last-first)/2;
  c = Ustrcmp(read_word, read_dir->name);
  if (c == 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(ERR32, read_word);
}


/*************************************************
*            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)
{
dirstr *first, *last;

next_word();
if (read_word[0] == 0) { error_moan(ERR105); return 0; }

first = read_stavedirlist;
last  = first + read_stavedirsize;

while (last > first)
  {
  int c;
  dirstr *d = first + (last-first)/2;
  c = Ustrcmp(read_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(ERR126, read_word);
return 0;
}

/* End of read4.c */
