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

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

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


/* This file contains part II of the code for reading in a PMW score file. It
deals with heading information. The main control function is last, preceded by
a table of heading directives which refers to earlier functions for processing
them. */


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


#define oo offsetof

#define int_u       1    /* unsigned */
#define int_s       2    /* signed */
#define int_rs      3    /* relative if signed */
#define int_less1   4    /* take away 1 (flag) */
#define int_f       8    /* fixed point (flag, must be last) */




/*************************************************
*            Global variable list                *
*************************************************/

/* Since we cannot put global addresses directly into the static table of
structures, we have to indirect them via a separate vector. (This restriction
is in the C language.) */

static int *global_vars[] = {
  (int *)(&main_magnification),
  (int *)(&main_maxvertjustify),
  (int *)(&midi_for_notes_off), 
  (int *)(&opt_oldbeambreak),
  (int *)(&opt_oldrestlevel),
  (int *)(&opt_oldstemlength),
  (int *)(&main_truepagelength),
  (int *)(&main_righttoleft), 
  (int *)(&main_sheetheight),
  (int *)(&main_sheetwidth),
  (int *)(&opt_stretchrule),
  (int *)(&main_kerning)
};

enum {
  glob_magnification,
  glob_maxvertjustify,
  glob_midifornotesoff, 
  glob_oldbeambreak,
  glob_oldrestlevel,
  glob_oldstemlength,
  glob_pagelength,
  glob_righttoleft, 
  glob_sheetdepth,
  glob_sheetwidth,
  glob_stretchrule,
  glob_kerning
};


/*************************************************
*          Local static variables                *
*************************************************/

static int read_map[stave_bitvec_size];  /* For reading lists of staves */


/*************************************************
*        Read and set font name, if present      *
*************************************************/

/* If a recognized font word ("roman", "italic", etc.) is the next thing in the
input, read it and put its index number into the current movement data
structure, at a given offset. If what follows is not a recognized font word, do
nothing.

Argument:   the offset for the result
Returns:    nothing
*/

static void 
set_fontname(int offset)
{
sigch();
if (isalpha(read_ch))
  {
  uschar *save_readchptr = read_chptr;
  int save_readch = read_ch;
  int x;
  if ((x = font_fontword(TRUE)) > 0)
    {
    *((int *)(((uschar *)curmovt) + offset)) = x;
    }
  else
    {
    read_ch = save_readch;
    read_chptr = save_readchptr;
    }
  }
}



/*************************************************
*           Read and set font size               *
*************************************************/

/* This is for the table of font sizes in the movement structure. If the next 
thing in the input is a digit, read it as a font size. If not, do nothing.

Arguments:
  offset      offset in current movement data structure for the result
  stretchOK   TRUE if stretch and/or shearing are permitted for the font
  
Returns:      nothing  
*/

static void 
set_fontsize(int offset, BOOL stretchOK)
{
sigch();
if (isdigit(read_ch))
  {
  if (!read_copied_fontsizestr)
    {
    fontsizestr *new = store_Xget(sizeof(fontsizestr));
    *new = *(curmovt->fontsizes);
    curmovt->fontsizes = new;
    read_copied_fontsizestr = TRUE;
    }
  *((int *)(((uschar *)(curmovt->fontsizes)) + offset)) = read_integer(TRUE);
  *((int **)(((uschar *)(curmovt->fontsizes)) + offset +
        offsetof(fontsizestr, fontmatrix_music))) = NULL;

  if (read_ch == '/')
    {
    int stretch;
    int shear = 0;
    next_ch();
    if (!read_expect_integer(&stretch, TRUE, FALSE)) return;
    if (read_ch == '/')
      {
      next_ch();
      if (!read_expect_integer(&shear, TRUE, TRUE)) return;
      }
    if (!stretchOK) error_moan(97, "allowed", "with this directive"); else
      {
      int *matrix = store_Xget(4*sizeof(int));
      *((int **)(((uschar *)(curmovt->fontsizes)) + offset +
        offsetof(fontsizestr, fontmatrix_music))) = matrix;
      matrix[0] = mac_muldiv(stretch, 65536, 1000);
      matrix[1] = 0;
      matrix[2] = (int)(tan(((double)shear)*atan(1.0)/45000.0)*65536.0);
      matrix[3] = 65536;
      }
    }
  }
}


/*******************************************************************
*  The following functions are referenced in the read_headstring   *
*  vector of heading directives. Each is called when the relevant  *
*  directive is encountered. They all have the same interface:     *
*  read_dir->arg1 and read_dir->arg2 contain up to two arguments   *
*  associated with the directive. There are no arguments to the    *
*  functions themselves, and they do not return any values.        *
******************************************************************/


/*************************************************
*           Deal with plain font size            *
*************************************************/

static void 
movt_fszproc(void)
{
sigch();
if (isdigit(read_ch)) set_fontsize(read_dir->arg1, read_dir->arg2);
  else error_moan(10, "Number");
}


/*************************************************
*           Deal with heading boolean            *
*************************************************/

static void 
movt_cboolproc(void)
{
*((CBOOL *)((uschar *)curmovt + read_dir->arg1)) = read_dir->arg2;
}


/*************************************************
*            Deal with heading font              *
*************************************************/

static void 
movt_fontproc(void)
{
int x;
set_fontsize(read_dir->arg2, TRUE);
if ((x = font_fontword(FALSE)) > 0)
  *((int *)(((uschar *)curmovt) + read_dir->arg1)) = x;
}


/*************************************************
*           Deal with global boolean             *
*************************************************/

static void 
opt_boolproc(void)
{
if (main_lastmovement == 1)
  *global_vars[read_dir->arg1] = read_dir->arg2;
else error_moan(20);
}


/*************************************************
*           Deal with heading integer            *
*************************************************/

static void 
movt_intproc(void)
{
int x;
int value = 0;
int flags = read_dir->arg2;
int offset = ((flags & int_less1) != 0)? -1 : 0;
int arg2 = flags & ~(int_f | int_less1);

int *address = (int *)((uschar *)curmovt + read_dir->arg1);
sigch();
if (arg2 == int_rs && (read_ch == '+' || read_ch == '-')) value = *address;
if (read_expect_integer(&x, flags >= int_f, arg2 != int_u))
  *address = value + x + offset;
}


/*************************************************
*           Deal with global integer            *
*************************************************/

static void 
opt_intproc(void)
{
int x;
int *address = global_vars[read_dir->arg1];
int value = 0;
int arg2 = (read_dir->arg2) & ~int_f;
sigch();
if (arg2 == int_rs && (read_ch == '+' || read_ch == '-')) value = *address;
if (read_expect_integer(&x, read_dir->arg2 >= int_f, arg2 != int_u))
  *address = value + x;
if (main_lastmovement != 1) error_moan(20);
}


/*************************************************
*          Deal with heading stave list          *
*************************************************/

static void 
movt_listproc(void)
{
stave_list **p = (stave_list **)(((uschar *)curmovt) + read_dir->arg1);
*p = NULL;

sigch();
while (isdigit(read_ch))
  {
  int s = read_integer(FALSE);
  int t = s;
  sigch();
  if (read_ch == '-')
    {
    next_sigch();
    if (!read_expect_integer(&t, FALSE, TRUE)) return;
    }

  if (t < s) error_moan(21);
    else if (t > max_stave) error_moan(22, max_stave+1);
  else
    {
    stave_list *q = store_Xget(sizeof(stave_list));
    q->next = NULL;
    q->first = s;
    q->last = t;
    *p = q;
    p = &(q->next);
    }

  sigch();
  if (read_ch == ',') next_sigch();
  }
}


/*************************************************
*          Deal with heading stave map           *
*************************************************/

static void 
movt_mapproc(void)
{
mac_initstave(read_map, 0);
if (read_dir->arg2 >= 0) mac_setstave(read_map, read_dir->arg2);

sigch();
while (isdigit(read_ch))
  {
  int s = read_integer(FALSE);
  int t = s;
  sigchNL();
  if (read_ch == '-')
    {
    next_sigch();
    if (!read_expect_integer(&t, FALSE, TRUE)) return;
    }

  if (t < s) error_moan(21);
    else if (t > max_stave) error_moan(22, max_stave + 1);
      else while (s <= t) { mac_setstave(read_map, s); s++; }  /* Can't put */
                                                               /* s++ inside */
  sigchNL();
  if (read_ch == ',') next_sigch();
  }

if (read_dir->arg1 != 0)
  memcpy(((uschar *)curmovt) + read_dir->arg1, read_map, 
    stave_bitvec_size*sizeof(int));
}


/*************************************************
*          Deal with heading/footing text        *
*************************************************/

/* The bits in the map remember which have been read, for flushing at the start
of a new movement. */

static void 
movt_headproc(void)
{
headstr **oldp = (headstr **)(((uschar *)curmovt) + read_dir->arg1);
headstr *new = store_Xget(sizeof(headstr));

/* If this is the first one of this type of heading, reset the pointer
so as not to copy the ones from the previous movement. */

if ((read_headmap & read_dir->arg2) == 0)
  {
  read_headmap |= (read_dir->arg2 & ~rh_ps);
  *oldp = NULL;
  }

/* Else find the end of the chain */

else while (*oldp != NULL) oldp = &((*oldp)->next);

/* Add the new block onto the chain and initialize */

*oldp = new;

read_headfootingtext(new, read_dir->arg2);
}


/*************************************************
*        Read arguments for heading/footing      *
*************************************************/

/* This code is also called from read4 for reading the text of footnotes and
sysnotes, so it has to be globally addressible. */

void 
read_headfootingtext(headstr *new, int type)
{
int defaultsize = 0;

new->next = NULL;
new->stretch = 0;
new->font = font_rm;

/* If the next character is a letter, we expect to read "draw <name>";
otherwise we expect an optional type size and a text string. */

sigch();
if (isalpha(read_ch))
  {
  tree_node *node;
  int argcount = 0;
  drawitem *drawargs = NULL;
  drawitem args[20];
  uschar word[80];
   
  read_word(word);
  if (Ustrcmp(word, "draw") != 0)
    {
    error_moan(10, "\"draw\"");
    return;
    }

  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);
    return;
    }
  new->a.drawing = node;
  new->b.args = drawargs;
  new->size = -1;
  }

/* Deal with conventional heading */

else
  {
  /* Set the type size, either by reading, or by algorithm. If explicit,
  can be followed by stretch and possibly shear, otherwise a NULL matrix. */

  new->matrix = NULL;
  if (isdigit(read_ch))
    {
    new->size = read_integer(TRUE);
    if (read_ch == '/')
      {
      int *matrix = store_Xget(4*sizeof(int));
      int stretch;
      int shear = 0;
      next_ch();
      if (!read_expect_integer(&stretch, TRUE, FALSE)) return;
      if (read_ch == '/')
        {
        next_ch();
        if (!read_expect_integer(&shear, TRUE, TRUE)) return;
        }
      new->matrix = matrix;
      matrix[0] = mac_muldiv(stretch, 65536, 1000);
      matrix[1] = 0;
      matrix[2] = (int)(tan(((double)shear)*atan(1.0)/45000.0)*65536.0);
      matrix[3] = 65536;
      }
    sigch();
    }
  else switch(type)
    {
    case rh_heading:
    new->size = read_headingsizes[read_headcount];
    break;

    case rh_footing:
    case rh_pagefooting:
    case rh_lastfooting:
    new->size = read_footingsize;
    break;

    case rh_pageheading:
    new->size = read_pageheadingsize;
    break;

    case rh_footnote:
    new->size = curmovt->fontsizes->fontsize_footnote;
    new->matrix = curmovt->fontsizes->fontmatrix_footnote;
    break;

    default:    /* PostScript headings/footings */
    new->size = 0;
    break;
    }

  /* Default size is type size */

  defaultsize = new->size;

  /* Must increment headcount whether explicit size supplied or not */

  if (type == rh_heading && read_headcount < read_maxheadcount)
    read_headcount++;

  /* We now expect a heading string; if not PostScript, check its escapes */

  new->a.text = string_read();
  new->b.spaceabove = 0;
  if (new->a.text != NULL && new->size != 0) 
    new->a.text = string_check(new->a.text);
  }

/* The stretch value is set to zero here; if required it will be set later. */

new->stretch = 0;

/* If another number follows, it is the space which follows; if not, default
it. */

sigch();
if (isdigit(read_ch) || read_ch == '+' || read_ch == '-')
  read_expect_integer(&(new->space), TRUE, TRUE);
else new->space = defaultsize;
}


/*************************************************
*            Accadjusts                          *
*************************************************/

static void 
accadjusts(void)
{
int i;
int *x = store_Xget(8*sizeof(int));
for (i = 0; i < 8; i++)
  {
  sigch();
  if (read_ch == ',') next_sigch();
  if (read_ch != '-' && read_ch != '+' && !isdigit(read_ch)) x[i] = 0;
    else (void)read_expect_integer(x+i, TRUE, TRUE);
  }
curmovt->accadjusts = x;
}


/*************************************************
*            Accspacing                          *
*************************************************/

static void 
accspacing(void)
{
int i;
int *x = store_Xget(6*sizeof(int));
for (i = 1; i < 6; i++)
  {
  sigch();
  if (read_ch == ',') next_sigch();
  if (!read_expect_integer(x+i, TRUE, TRUE)) break;
  }
curmovt->accspacing = x;
}


/*************************************************
*               Barlinespace                     *
*************************************************/

static void 
barlinespace(void)
{
sigch();
if (read_ch == '*')
  {
  curmovt->barlinespace = 0x80000000;
  next_sigch();
  }
else movt_intproc();
}


/*************************************************
*              Barnumberlevel                    *
*************************************************/

/* A sign is mandatory */

static void 
barnumberlevel(void)
{
sigch();
if (read_ch != '+' && read_ch != '-') error_moan(10, "\"+\" or \"-\"");
  else movt_intproc();
}


/*************************************************
*            Barnumbers                          *
*************************************************/

static void 
barnumbers(void)
{
int wordread = FALSE;
uschar word[80];
curmovt->barno_textflags = 0;
sigch();
if (isalpha(read_ch))
  {
  read_word(word);
  sigch();
  if (Ustrcmp(word, "boxed") == 0) curmovt->barno_textflags = text_box;
    else if (Ustrcmp(word, "ringed") == 0) curmovt->barno_textflags = text_ring;
      else wordread = TRUE;
  }

if (!wordread && isalpha(read_ch))
  { read_word(word); wordread = TRUE; }

if (wordread)
  {
  if (Ustrcmp(word, "line") == 0) curmovt->barno_interval = -1;
    else { error_moan(10, "\"line\""); return; }
  }
else
  {
  if (!read_expect_integer(&(curmovt->barno_interval), FALSE, FALSE)) return;
  }

set_fontsize(oo(fontsizestr, fontsize_barno), TRUE);
set_fontname(oo(movtstr, font_barnumber));
}


/*************************************************
*              Bracestyle                        *
*************************************************/

static void 
bracestyle(void)
{
movt_intproc();
if (curmovt->bracestyle > 1)
  {
  error_moan(37, "Number less than 2");
  curmovt->bracestyle = 0;
  }
}


/*************************************************
*            Breakbarlines                       *
*************************************************/

static void 
breakbarlines(void)
{
sigch();
if (!isdigit(read_ch))
  {
  int *map = (int *)(((uschar *)curmovt) + read_dir->arg1);
  mac_initstave(map, -1);
  }
else movt_mapproc();
curmovt->fullbarend = FALSE;
}


/*************************************************
*            Breakbarlinesx                      *
*************************************************/

static void 
breakbarlinesx(void)
{
breakbarlines();
curmovt->fullbarend = TRUE;
}


/*************************************************
*              Caesurastyle                      *
*************************************************/

static void 
caesurastyle(void)
{
movt_intproc();
if (curmovt->caesurastyle > 1)
  {
  error_moan(37, "Number less than 2");
  curmovt->caesurastyle = 0;
  }
}


/*************************************************
*             Clef size (relative)               *
*************************************************/

static void 
clefsize(void)
{
sigch();
if (isdigit(read_ch))
  {
  set_fontsize(offsetof(fontsizestr,fontsize_clefs), FALSE);
  (curmovt->fontsizes)->fontsize_clefs *= 10;
  }
else error_moan(10, "Number");
}


/************************************************
*            Clef style                         *
************************************************/

static void 
clefstyle(void)
{
movt_intproc();
if (curmovt->clefstyle > 3) error_moan(10, "Number less than 4");
}


/************************************************
*             Clef widths                       *
************************************************/

static int shape[] = { clef_treble, clef_bass, clef_alto, clef_h, clef_none };

static void 
clefwidths(void)
{
int i;
sigch();
for (i = 0; i < 5 && isdigit(read_ch); i++)
  {
  int width, j; 
  (void)read_expect_integer(&width, FALSE, FALSE);
  if (read_ch == ',') next_ch();
  sigch();
  for (j = 0; j < clef_count; j++)
    if (main_cleftypes[j] == shape[i]) curmovt->clefwidths[j] = width;
  } 
}


/*************************************************
*                Copyzero                        *
*************************************************/

static void 
copyzero(void)
{
zcopystr **pp = &(curmovt->zcopy);
sigch();

if (!isdigit(read_ch)) { error_moan(10, "stave number"); return; }

while (isdigit(read_ch))
  {
  zcopystr *p = store_Xget(sizeof(zcopystr));
  *pp = p;
  pp = &(p->next);
  p->next = NULL;
  p->baradjust = 0;
  p->stavenumber = read_integer(FALSE);
  if (read_ch == '/')
    {
    next_ch();
    if (!read_expect_integer(&(p->adjust), TRUE, TRUE)) break;
    }
  else p->adjust = 0;
  sigch();
  if (read_ch == ',') next_sigch();
  }
}


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

static void 
doublenotes(void)
{
main_notenum *= 2;
curmovt->time = read_scaletime(curmovt->time);
}


/*************************************************
*               Gracespacing                     *
*************************************************/

/* Read up to two fixed point numbers, second defaulting to the first; "+" and
"-" can be used to adjust values. */

static void 
gracespacing(void)
{
int arg;
int value = 0;
BOOL wasrelative = FALSE;

sigch();
if (read_ch == '+' || read_ch == '-') 
  {
  value = curmovt->gracespacing[0];
  wasrelative = TRUE; 
  } 
if (!read_expect_integer(&arg, TRUE, TRUE)) return;
curmovt->gracespacing[0] = value + arg;
    
sigch();
if (read_ch == ',') next_ch();
sigch();
if (!isdigit(read_ch) && read_ch != '+' && read_ch != '-') 
  {
  curmovt->gracespacing[1] = wasrelative?
    curmovt->gracespacing[1] + arg : 
    curmovt->gracespacing[0];
  return;
  } 

value = (read_ch == '+' || read_ch == '-')? curmovt->gracespacing[1] : 0;
if (!read_expect_integer(&arg, TRUE, TRUE)) return;
curmovt->gracespacing[1] = value + arg;
}


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

static void 
halvenotes(void)
{
if (main_notenum > 1) main_notenum /= 2; else main_noteden *= 2;
curmovt->time = read_scaletime(curmovt->time);
}


/*************************************************
*            Hyphenstring                        *
*************************************************/

static void 
hyphenstring(void)
{
uschar s[256];
if (read_plainstring(s))
  curmovt->hyphenstring = store_copystring(s);
else error_moan(10, "String");
}


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

static void 
justify(void)
{
int yield = 0;
for (;;)
  {
  sigch();
  if (isalpha(read_ch))
    {
    uschar word[256];
    uschar *backupptr = read_chptr;
    int  backup = read_ch;
    read_word(word);
    if (Ustrcmp(word, "top") == 0)         yield |= just_top;
    else if (Ustrcmp(word, "bottom") == 0) yield |= just_bottom;
    else if (Ustrcmp(word, "left") == 0)   yield |= just_left;
    else if (Ustrcmp(word, "right") == 0)  yield |= just_right;
    else if (Ustrcmp(word, "all") == 0)    yield |= just_all;
    else { read_chptr = backupptr; read_ch = backup; break; }
    }
  else break;
  }
curmovt->justify = yield;
}


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

/* After setting the key for this movement, ensure the relevant stave data is
fudged, in case there are transposed text strings in the subsequent header
lines. The transpose value will already be fudged. */

static void 
key(void)
{
curmovt->key = read_key();
stave_key = curmovt->key;
stave_key_tp = transpose_key(stave_key, stave_transpose, TRUE);
}


/*************************************************
*                 Landscape                      *
*************************************************/

static void 
landscape(void)
{
int temp = curmovt->truelinelength;
curmovt->truelinelength = main_truepagelength;
main_truepagelength = temp;
if (opt_sheetsize == sheet_A5) main_truepagelength -= 28000;

temp = main_sheetwidth;
main_sheetwidth = main_sheetheight;
main_sheetheight = temp;
opt_landscape = TRUE;
if (main_lastmovement != 1) error_moan(20);
}


/*************************************************
*                Layout                          *
*************************************************/

static void 
layout(void)
{
int *temp = store_Xget(layout_maxsize * 2 * sizeof(int));
int stack[20];
int ptr = 0;
int level = 0;

for (;;)
  {
  int value;
  sigch();
  if (!isdigit(read_ch))
    {
    if (ptr == 0 || level > 0) { error_moan(10, "Number"); return; }

    /* Final item is always repeat back to start */

    temp[ptr++] = lv_repeatptr;
    temp[ptr++] = 0;

    /* Save in correct size piece of store */

    curmovt->layout = store_Xget(ptr*sizeof(int));
    memcpy(curmovt->layout, temp, ptr * sizeof(int));
    store_free(temp);
    return;
    }

  /* Value must be > 0 */

  read_expect_integer(&value, FALSE, FALSE);
  if (value == 0)
    {
    error_moan(16, "Zero value changed to 1");
    value = 1;
    }

  /* If number followed by '(' it is a repeat count */

  sigch();
  if (read_ch == '(')
    {
    next_ch();
    temp[ptr++] = lv_repeatcount;
    temp[ptr++] = value;
    stack[level++] = ptr;
    }

  /* Else it is a barcount value, with varying terminators. If none of the
  specials, it does nothing, and another number will be a continuation, while
  anything else is the next directive. There may be a number of these
  terminators. */

  else
    {
    temp[ptr++] = lv_barcount;
    temp[ptr++] = value;

    for (;;)
      {
      if (read_ch == ',') { next_sigch(); break; }

      /* Close bracket is the end of a repeat. Check nesting. It can be followed
      by comma, semicolon, or another bracket. */

      else if (read_ch == ')')
        {
        if (level <= 0) { error_moan(10, "Bracket not"); return; } else
          {
          temp[ptr++] = lv_repeatptr;
          temp[ptr++] = stack[--level];
          }
        next_sigch();
        }

      /* Semicolon generates a new page item */

      else if (read_ch == ';')
        {
        temp[ptr++] = lv_newpage;
        next_sigch();
        }

      /* Anything else, just carry on with the big loop */

      else break;
      }
    }
  }
}


/*************************************************
*               Ledgerstyle                      *
*************************************************/

/* The table sets pointers so the value goes into the curmovt->ledger variable.
Translate 0/1 into the old/new ledger characters. */

static void
ledgerstyle(void)
{
movt_intproc();
curmovt->ledger = (curmovt->ledger == 0)? '=' : 184;
}


/*************************************************
*             Maxbeamslope                       *
*************************************************/

static void 
maxbeamslope(void)
{
if (!read_expect_integer(&curmovt->maxbeamslope1, TRUE, FALSE)) return;
if (!read_expect_integer(&curmovt->maxbeamslope2, TRUE, FALSE)) return;
}


/*************************************************
*              Midichannel                       *
*************************************************/

/* Local subroutine to make a copy of a map if this is the first time it's been
updated in this movement. */

static void 
copy_midi_map(int flag, int size, uschar **anchor)
{
if ((read_headmap & flag) == 0)
  {
  uschar *new = store_Xget(size);
  memcpy(new, *anchor, size);
  *anchor = new;
  read_headmap |= flag;
  }
}

/* The main function */

static void 
midichannel(void)
{
int channel;
uschar string[80];

debug_printf("midichannel start\n");

/* A channel number is always expected */

if (!read_expect_integer(&channel, FALSE, FALSE)) return;

if (channel < 1 || channel > midi_maxchannel)
  {
  error_moan(109, "channel", midi_maxchannel);
  channel = 1;
  }
  
/* Deal with an optional voice setting */

if (read_plainstring(string))
  {
  int voicenumber;
  if (string[0] == 0) voicenumber = 129; else  /* => don't do MIDI voice setting */
    {
    if (string[0] == '#') voicenumber = Uatoi(string+1);
      else voicenumber = read_getmidinumber(midi_voicenames, string, US"voice");
    if (voicenumber < 1 || voicenumber > 128)
      {
      if (midi_filename != NULL) error_moan(109, "voice", 128);
      voicenumber = 1;
      }
    }

  copy_midi_map(rh_midivoice, midi_maxchannel, &(curmovt->midi_voice));
  curmovt->midi_voice[channel-1] = voicenumber - 1;

  /* There may be an optional volume setting */

  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
        {
        copy_midi_map(rh_midivolume, max_stave+1, &(curmovt->midi_volume));
        curmovt->midi_volume[channel-1] = vol;
        }
      }
    }
  }

/* Deal with an optional stave list */

sigch();
if (isdigit(read_ch))
  {
  int i;
  int pitch = 0;

  copy_midi_map(rh_midichannel, max_stave+1, &(curmovt->midi_channel));

  movt_mapproc();    /* read stave list into read_map */

  /* Deal with optional 'pitch' forcing */

  if (read_plainstring(string))
    {
    if (string[0] == 0) pitch = 0;  /* => don't do MIDI pitch forcing */
      else if (string[0] == '#') pitch = Uatoi(string+1);
        else pitch = read_getmidinumber(midi_percnames, string, US"percussion instrument");
    copy_midi_map(rh_midinote, max_stave+1, &(curmovt->midi_note));
    }

  /* Now update the per-stave data */

  for (i = 1; i <= max_stave; i++)
    {
    if (mac_teststave(read_map, i))
      {
      if (pitch) curmovt->midi_note[i] = pitch;
      curmovt->midi_channel[i] = channel;
      }
    }
  }
   
debug_printf("midichannel end\n"); 
}


/*************************************************
*               Midistart                        *
*************************************************/

static void 
midistart(void)
{
int max = 0;
int count = 0;
int *temp = NULL;

for (;;)
  {
  int value;
  sigch();
  if (!isdigit(read_ch))
    {
    if (count == 0) { error_moan(10, "Number"); return; }
    temp[0] = count;
    curmovt->midi_start = temp;
    return;
    }

  /* Get more store if needed */
  
  if (++count > max) 
    {
    int newmax = max + 20;
    int *new = store_Xget((newmax + 1) * sizeof(int));
    if (max > 0) 
      {
      memcpy(new, temp, (max+1) * sizeof(int));
      store_free(temp);
      } 
    temp = new;
    max = newmax;   
    }  

  read_expect_integer(&value, FALSE, FALSE);
  temp[count] = value;
  } 
}


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

static void 
notespacing(void)
{
int i;
sigch();
if (read_ch == '*')
  {
  int f;
  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);
    }
  for (i = 0; i < 8; i++)
    curmovt->notespacing[i] = (f * curmovt->notespacing[i])/1000;
  }

else
  {
  for (i = 0; i < 8; i++)
    {
    if (!read_expect_integer(main_notespacing+i, TRUE, FALSE)) return;
    curmovt->notespacing[i] = main_notespacing[i];
    sigch();
    if (read_ch == ',') next_ch();
    }
  }
}


/*************************************************
*            Oldstretchrule                      *
*************************************************/

/* This used to be a standard boolean, but it's now extended to an integer 
option called "stretchrule". This is a backwards-compatibility function. */

static void
oldstretchrule(void)
{
opt_stretchrule = 0;
}


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

static void 
page(void)
{
main_pageinc = 1;
if (main_lastmovement != 1) { error_moan(20); return; }
if (!read_expect_integer(&main_firstpage, FALSE, FALSE)) return;
sigch();
if (!isdigit(read_ch)) return;
read_expect_integer(&main_pageinc, FALSE, FALSE);
}



/*************************************************
*               Playtempo                        *
*************************************************/

static void 
playtempo(void)
{
movt_intproc();           /* Read a single number */

/* Now look for additional data giving tempo changes within a movement */

sigch();
if (read_ch == ',') next_sigch();

if (isdigit(read_ch))
  {
  BOOL barerror = FALSE;
  int ii;
  int i = 0;
  int lastbar = 0;
  int *copylist;
  int list[100];

  while (isdigit(read_ch))
    {
    int bar = read_integer(TRUE);
    int tempo;
    if (read_ch != '/')
      {
      error_moan(10, "/");
      break;
      }
    next_ch();
    if (!read_expect_integer(&tempo, FALSE, FALSE)) break;

    if (bar <= lastbar)
      {
      if (!barerror) { error_moan(86); barerror = TRUE; }
      }
    lastbar = bar;

    if (i > 99)
      {
      error_moan(85, 50);
      break;
      }

    list[i++] = bar;
    list[i++] = tempo;

    sigch();
    if (read_ch == ',') next_sigch();
    }

  copylist = store_get((i+1) * sizeof(int));
  for (ii = 0; ii < i; ii++) copylist[ii] = list[ii];
  copylist[ii] = BIGNUMBER;
  curmovt->play_tempo_changes = copylist;
  }
}


/*************************************************
*              Playtranspose                     *
*************************************************/

static void 
playtranspose(void)
{
sigch();
while (isdigit(read_ch))
  {
  int amount;
  int stave = read_integer(FALSE);
  sigch();

  if (read_ch != '/')
    {
    error_moan(10, "/");
    return;
    }

  next_ch();
  if (!read_expect_integer(&amount, FALSE, TRUE)) return;
  sigch();
  (curmovt->playtranspose)[stave] = amount;
  if (read_ch == ',') next_ch();
  sigch();
  }
}


/*************************************************
*               Playvolume                       *
*************************************************/

static void 
playvolume()
{
int i, v;
uschar *vv;

if (!read_expect_integer(&v, FALSE, FALSE)) return;  /* Default setting */

if (v > 15)
  {
  error_moan(10, "Number between 0 and 15");
  return;
  }

curmovt->play_volume = vv = store_Xget(max_stave + 1);
for (i = 1; i <= max_stave; i++) vv[i] = v;

/* Now look for additional data giving stave volumes */

sigch();
if (read_ch == ',') next_sigch();

while (isdigit(read_ch))
  {
  int stave = read_integer(FALSE);
  if (read_ch != '/')
    {
    error_moan(10, "/");
    break;
    }
  next_ch();
  if (!read_expect_integer(&v, FALSE, FALSE)) break;

  if (v > 15)
    {
    error_moan(10, "Number between 0 and 15");
    break;
    }

  vv[stave] = v;
  sigch();
  if (read_ch == ',') next_sigch();
  }
}


/*************************************************
*             PMW version check                  *
*************************************************/

static void 
pmwversion(void)
{
int v;
if (!read_expect_integer(&v, TRUE, FALSE)) return;
if (v != version_fixed) error_moan(28, v, version_fixed);
}


/*************************************************
*                Printtime                       *
*************************************************/

/* Local subroutine to deal with one string possibly followed by /s and a
number.

Arguments:
  s          pointer to where to put a pointer to the string
  offset     pointer to where to put the font offset
  
Returns:     TRUE if all goes well; FALSE on error  
*/

static BOOL 
ptstring(uschar **s, uschar *offset)
{
*s = string_check(string_read());

if (read_ch == '/')
  {
  int oo;
  next_ch();
  if (read_ch == 's')
    {
    next_ch();
    if (!read_expect_integer(&oo, FALSE, FALSE)) return FALSE;
    if ((oo -= 1) >= MaxTextFont) error_moan(39, MaxTextFont);
    }
  else
    {
    error_moan(10, "/s");
    return FALSE;
    }
  *offset = oo;
  }
else *offset = ff_offset_ts;

return TRUE;
}


/* The actual routine */

static void 
printtime(void)
{
ptimestr *p = store_Xget(sizeof(ptimestr));
p->next = main_printtime;
main_printtime = p;
if ((p->time = read_time()) == 0) return;
if (!ptstring(&(p->top), &(p->offsettop))) return;
(void)ptstring(&(p->bot), &(p->offsetbot));
}


/*************************************************
*                Printkey                        *
*************************************************/

static void
printkey(void)
{
pkeystr *p = store_Xget(sizeof(pkeystr));
p->next = main_printkey;
main_printkey = p;
p->key = read_key();
p->clef = read_clef();
p->string = string_check(string_read());
}




/*************************************************
*                 Pssetup                        *
*************************************************/

static void 
pssetup(void)
{
if (main_lastmovement != 1) error_moan(20); else
  {
  uschar s[256];
  headstr *h = main_pssetup;
  headstr **hh = &main_pssetup;

  while (h != NULL)
    {
    hh = &(h->next);
    h = *hh;
    }

  if (read_plainstring(s))
    {
    h = store_Xget(sizeof(headstr));
    h->next = NULL;
    h->size = -1;
    h->a.text = store_copystring(s);
    h->b.spaceabove = 0;
    *hh = h;
    }

  else error_moan(10, "String");
  }
}


/*************************************************
*              Rehearsalmarks                    *
*************************************************/

static void 
rehearsalmarks(void)
{
uschar word[80];
sigch();
if (isalpha(read_ch))
  {
  read_word(word);
  sigch();
  if (Ustrcmp(word, "boxed") == 0) curmovt->rehearsalstyle = text_box;
    else if (Ustrcmp(word, "ringed") == 0) curmovt->rehearsalstyle = text_ring;
      else if (Ustrcmp(word, "plain") == 0) curmovt->rehearsalstyle = 0;
        else error_moan(10, "\"boxed\", \"ringed\", or \"plain\"");
  }
set_fontsize(oo(fontsizestr, fontsize_rehearse), TRUE);
set_fontname(oo(movtstr, font_rehearse));
}


/*************************************************
*        Sheetdepth and sheetwidth               *
*************************************************/

static void 
sheetdim(void)
{
opt_sheetsize = sheet_unknown;
opt_intproc();
}


/*************************************************
*                 Sheetsize                      *
*************************************************/

static void 
sheetsize(void)
{
uschar word[80];
read_word(word);
if (Ustrcmp(word, "a4") == 0 || Ustrcmp(word, "A4") == 0)
  {
  curmovt->truelinelength = 480000;
  main_truepagelength = 720000;
  main_sheetwidth = 595000;
  main_sheetheight = 842000;
  opt_sheetsize = sheet_A4;
  }
else if (Ustrcmp(word, "a3") == 0 || Ustrcmp(word, "A3") == 0)
  {
  curmovt->truelinelength = 730000;
  main_truepagelength = 1060000;
  main_sheetwidth = 842000;
  main_sheetheight = 1190000;
  opt_sheetsize = sheet_A3;
  }
else if (Ustrcmp(word, "a5") == 0 || Ustrcmp(word, "A5") == 0)
  {
  curmovt->truelinelength = 366000;
  main_truepagelength = 480000;
  main_sheetwidth = 421000;
  main_sheetheight = 595000;
  opt_sheetsize = sheet_A5;
  }
else if (Ustrcmp(word, "b5") == 0 || Ustrcmp(word, "B5") == 0)
  {
  curmovt->truelinelength = 420000;
  main_truepagelength = 590000;
  main_sheetwidth = 499000;
  main_sheetheight = 709000;
  opt_sheetsize = sheet_B5;
  }
else if (Ustrcmp(word, "letter") == 0)
  {
  curmovt->truelinelength = 500000;
  main_truepagelength = 670000;
  main_sheetwidth = 612000;
  main_sheetheight = 792000;
  opt_sheetsize = sheet_letter;
  }
else error_moan(10, "\"A3\", \"A4\", \"A5\", \"B5\", or \"letter\"");
if (main_lastmovement != 1) error_moan(20);
}


/*************************************************
*               Startbracketbar                  *
*************************************************/

static void 
startbracketbar(void)
{
sigch();
if (isalpha(read_ch))
  {
  uschar word[80];
  read_word(word);
  if (Ustrcmp(word, "join") == 0) curmovt->startjoin = TRUE;
    else if (Ustrcmp(word, "nojoin") == 0) curmovt->startjoin = FALSE;
      else { error_moan(10, "\"join\" or \"nojoin\""); return; }
  }

read_expect_integer(&curmovt->startbracketbar, FALSE, FALSE);
}


/*************************************************
*               Stavesize                        *
*************************************************/

static void 
stavesize(void)
{
int *stavesizes = store_Xget((max_stave+1)*sizeof(int));
memcpy(stavesizes, curmovt->stavesizes, (max_stave+1)*sizeof(int));
curmovt->stavesizes = stavesizes;

sigch();
while (isdigit(read_ch))
  {
  int size;
  int stave = read_integer(FALSE);
  if (stave > max_stave) { error_moan(22, max_stave+1); stave = 1; }
  if (read_ch != '/') { error_moan(10, "/"); return; }
  next_ch();
  if (!read_expect_integer(&size, TRUE, FALSE)) return;
  stavesizes[stave] = size;
  sigch();
  if (read_ch == ',') next_sigch();
  }
}


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

static void 
stavespacing(void)
{
BOOL first = TRUE;
int i;
int *newsp = store_Xget((max_stave+1) * sizeof(int));
int *newen = store_Xget((max_stave+1) * sizeof(int));
unsigned int done[stave_bitvec_size];

newsp[0] = newen[0] = 0;
mac_initstave(done, 0);

for (i = 1; i <= max_stave; i++)
  {
  newsp[i] = 44000;
  newen[i] = 0;
  }

sigch();
while (isdigit(read_ch))
  {
  int space;
  int ensure = 0;
  int stave = read_integer(FALSE);

  sigch();

  if (read_ch != '/')
    {
    if (first)
      {
      int space = stave*1000;
      if (read_ch == '.') space += read_integer(TRUE);
      for (i = 1; i <= max_stave; i++) newsp[i] = space;
      goto NEXT;
      }
    else
      {
      error_moan(10, "/");
      return;
      }
    }

  next_ch();
  if (!read_expect_integer(&space, TRUE, FALSE)) return;
  sigch();
  if (read_ch == '/')
    {
    ensure = space;
    next_ch();
    if (!read_expect_integer(&space, TRUE, FALSE)) return;
    }
    
  if (stave == 0) error_moan(107); else
    { 
    if (mac_teststave(done, stave)) error_moan(106, stave, US"stavespacing");
    mac_setstave(done, stave); 
    newsp[stave] = space;
    newen[stave] = ensure;
    } 

NEXT:

  if (read_ch == ',') next_ch();
  sigch();
  first = FALSE;
  }

curmovt->stave_spacing = newsp;
curmovt->stave_ensure = newen;
}


/*************************************************
*               Startlinespacing                 *
*************************************************/

static void 
startlinespacing(void)
{
startlinestr *p = store_Xget(sizeof(startlinestr));
p->clefspace = p->keyspace = p->timespace = p->notespace = 0;

sigch();
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->clefspace), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->keyspace), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->timespace), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->notespace), TRUE, TRUE);
  }

curmovt->startline = p;
}


/*************************************************
*                Stemswap                        *
*************************************************/

static void 
stemswap(void)
{
uschar word[100];
read_word(word);
if (Ustrcmp(word, "up") == 0) curmovt->stemswaptype = stemswap_up;
else if (Ustrcmp(word, "down") == 0) curmovt->stemswaptype = stemswap_down;
else if (Ustrcmp(word, "left") == 0) curmovt->stemswaptype = stemswap_left;
else if (Ustrcmp(word, "right") == 0) curmovt->stemswaptype = stemswap_right;
else error_moan(10, "\"up\", \"down\", \"left\", or \"right\"");
}


/*************************************************
*              Stemswaplevel                     *
*************************************************/

static void 
stemswaplevel(void)
{
int i, x;
int *new = store_Xget((max_stave+1) * sizeof(int));
for (i = 0; i <= max_stave; i++) new[i] = P_3L;

if (!read_expect_integer(&x, FALSE, TRUE)) return;

if (x > 0 && read_ch == '/')
  {
  for (;;)
    {
    int y;
    next_ch();
    if (!read_expect_integer(&y, FALSE, TRUE)) return;
    new[x] = P_3L + y*2;
    if (read_ch == ',') next_ch();
    sigch();
    if (!isdigit(read_ch)) break;
    x = read_integer(FALSE);
    if (read_ch != '/') { error_moan(10, "/"); return; }
    }
  }

else for (i = 0; i <= max_stave; i++) new[i] = P_3L + x*2;

curmovt->stemswaplevel = new;
}


/************************************************
*            Stem lengths                       *
************************************************/

static void 
stemlengths(void)
{
int i;
sigch();
for (i = 2; i < 8 && isdigit(read_ch); i++)
  {
  (void)read_expect_integer(&(curmovt->tailadjusts[i]), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  } 
}


/*************************************************
*                Textfont                        *
*************************************************/

/* This also handles musicfont, if arg1 is non-zero. */

static void 
textfont(void)
{
int fontid, n;
uschar s[100];
uschar s2[100];
uschar *p;

/* Font selection is permitted only in the first movement */

if (main_lastmovement != 1) { error_moan(20); return; }

/* If font id given, use it, else read a word */

if (read_dir->arg1) fontid = read_dir->arg1; else
  {
  if ((fontid = font_fontword(FALSE)) < 0) return;
  }

/* Read the first font name, which must be present, and the
optional second name for the PostScript font. */

if (!read_plainstring(s)) { error_moan(10, "String"); return; }
if (read_ch == ',') next_ch();
p = read_plainstring(s2)? s2 : s;

/* See if is already in the font list. If it is, we set its
number in the font table and return. If not, we set it up
as a new font. */

n = font_search(s);
if (n >= 0) font_table[fontid] = n; else
  {
  fontstr *fs;
  if (font_count >= max_fonts) { error_moan(26, max_fonts); return; }
  fs = &(font_List[font_count]);
  fs->psname = store_copystring(s);
  fs->widths = NULL; 
  fs->high_tree = NULL; 
  fs->heights = NULL; 
  fs->kerns = NULL;
  fs->kerncount = -1; 
  fs->stdencoding = fs->fixedpitch = fs->hasfi = FALSE; 
  font_table[fontid] = font_count;
  font_loadtables(font_count++);
  }

/* Ensure both music fonts are the same */

if (fontid == font_mf) font_table[font_mu] = font_table[font_mf];
}


/*************************************************
*            Textsizes                           *
*************************************************/

static void 
textsizes(void)
{
int i;
for (i = 0; i < MaxTextFont; i++)
  {
  set_fontsize(offsetof(fontsizestr,fontsize_text) + i*sizeof(int), TRUE);
  if (read_ch == ',') next_ch();
  }
}


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

static void 
timeproc(void)
{
int t = read_time();            /* returns 0 after giving error */
if (t != 0) curmovt->time = t;
}



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

static void 
transpose(void)
{
int temp = curmovt->transpose;
movt_intproc();
if (temp < max_transpose) curmovt->transpose += temp;
  
/* In case this is followed by heading/footing lines that contain transposed
text, fudge up some fake stave values. The stave_key will already be set. */

stave_transpose = curmovt->transpose;
stave_key_tp = transpose_key(stave_key, stave_transpose, TRUE);
}


/*************************************************
*               Transposed key                   *
*************************************************/

static void 
transposedkey(void)
{
int oldkey = read_key();
uschar word[80];

read_word(word);
if (Ustrcmp(word, "use") != 0) error_moan(10, "\"use\""); else
  {
  int newkey = read_key();
  trkeystr *k = store_Xget(sizeof(trkeystr));
  k->oldkey = oldkey;
  k->newkey = newkey;
  k->next = main_transposedkeys;
  main_transposedkeys = k;
  }
}



/*************************************************
*             Transposed accidental option       *
*************************************************/

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


/*************************************************
*             Trill string                       *
*************************************************/

static void 
trillstring(void)
{
uschar s[256];
sigch();
if (isdigit(read_ch)) set_fontsize(oo(fontsizestr,fontsize_trill), TRUE);
if (read_plainstring(s))
  curmovt->trillstring = store_copystring(s);
else error_moan(10, "String");
}



/*************************************************
*            Heading directive list              *
*************************************************/

/* The function for reading a draw list is held separately for convenience. */

static dirstr read_headlist[] = {
  { "accadjusts",       accadjusts,     0, 0 },
  { "accspacing",       accspacing,     0, 0 },
  { "bar",              movt_intproc,   oo(movtstr,baroffset), int_u+int_less1 },
  { "barcount",         movt_intproc,   oo(movtstr,maxbarcount),  int_u },
  { "barlinesize",      movt_intproc,   oo(movtstr,barlinesize), int_u+int_f },
  { "barlinespace",     barlinespace,   oo(movtstr,barlinespace), int_u+int_f },
  { "barlinestyle",     movt_intproc,   oo(movtstr,barlinestyle), int_u },
  { "barnumberlevel",   barnumberlevel, oo(movtstr,barno_level), int_rs+int_f },
  { "barnumbers",       barnumbers,     0, 0 },
  { "beamendrests",     movt_cboolproc, oo(movtstr,beamendrests), TRUE },
  { "beamflaglength",   movt_intproc,   oo(movtstr,beamflaglength), int_u+int_f },  
  { "beamthickness",    movt_intproc,   oo(movtstr,beamdepth), int_u+int_f },
  { "bottommargin",     movt_intproc,   oo(movtstr,botmargin), int_u+int_f },
  { "brace",            movt_listproc,  oo(movtstr,bracelist), 0 },
  { "bracestyle",       bracestyle,     oo(movtstr,bracestyle), int_u },
  { "bracket",          movt_listproc,  oo(movtstr,bracketlist), 0 },
  { "breakbarlines",    breakbarlines,  oo(movtstr,breakbarlines), -1 },
  { "breakbarlinesx",   breakbarlinesx, oo(movtstr,breakbarlines), -1 },
  { "breveledgerextra", movt_intproc,   oo(movtstr,breveledgerextra),int_u+int_f},
  { "breverests",       movt_cboolproc, oo(movtstr,breverests), TRUE },
  { "caesurastyle",     caesurastyle,   oo(movtstr,caesurastyle), int_u },
  { "check",            movt_cboolproc, oo(movtstr,check), TRUE },
  { "checkdoublebars",  movt_cboolproc, oo(movtstr,checkdoublebars), TRUE },
  { "clefsize",         clefsize,       0, 0 },
  { "clefstyle",        clefstyle,      oo(movtstr,clefstyle), int_u },
  { "clefwidths",       clefwidths,     0, 0 },
  { "copyzero",         copyzero,       0, 0 },
  { "cuegracesize",     movt_fszproc,   oo(fontsizestr,fontsize_cuegrace), FALSE },
  { "cuesize",          movt_fszproc,   oo(fontsizestr,fontsize_cue), FALSE },
  { "dotspacefactor",   movt_intproc,   oo(movtstr,dotspacefactor), int_u+int_f },
  { "doublenotes",      doublenotes,    0, 0 },
  { "draw",             read_draw,      0, 0 },
  { "endlinesluradjust",movt_intproc,   oo(movtstr,endlinesluradjust), int_f },
  { "endlineslurstyle", movt_intproc,   oo(movtstr,endlineslurstyle), int_u },
  { "endlinetieadjust", movt_intproc,   oo(movtstr,endlinetieadjust), int_f },
  { "endlinetiestyle",  movt_intproc,   oo(movtstr,endlinetiestyle), int_u },
  { "extenderlevel",    movt_intproc,   oo(movtstr,extenderlevel), int_f },
  { "fbsize",           movt_fszproc,   oo(fontsizestr,fontsize_fbass), TRUE },
  { "footing",          movt_headproc,  oo(movtstr,footing), rh_footing },
  { "footnotesep",      movt_intproc,   oo(movtstr,footnotesep), int_f },
  { "footnotesize",     movt_fszproc,   oo(fontsizestr,fontsize_footnote), TRUE },
  { "gracesize",        movt_fszproc,   oo(fontsizestr,fontsize_grace), FALSE },
  { "gracespacing",     gracespacing,   0, 0 }, 
  { "gracestyle",       movt_intproc,   oo(movtstr,gracestyle), int_u },
  { "hairpinlinewidth", movt_intproc,   oo(movtstr,hairpinlinewidth), int_rs+int_f },
  { "hairpinwidth",     movt_intproc,   oo(movtstr,hairpinwidth), int_rs+int_f },
  { "halfflatstyle",    movt_intproc,   oo(movtstr,hflatstyle), int_u },
  { "halfsharpstyle",   movt_intproc,   oo(movtstr,hsharpstyle), int_u },
  { "halvenotes",       halvenotes,     0, 0 },
  { "heading",          movt_headproc,  oo(movtstr,heading), rh_heading },
  { "hyphenstring",     hyphenstring,   0, 0 },
  { "hyphenthreshold",  movt_intproc,   oo(movtstr,hyphenthreshold), int_rs+int_f },
  { "join",             movt_listproc,  oo(movtstr,joinlist), 0 },
  { "joindotted",       movt_listproc,  oo(movtstr,joindottedlist), 0 },
  { "justify",          justify,        0, 0 },
  { "key",              key,            0, 0 },
  { "keydoublebar",     movt_cboolproc, oo(movtstr,keydoublebar), TRUE },
  { "keysinglebar",     movt_cboolproc, oo(movtstr,keydoublebar), FALSE },
  { "keywarn",          movt_cboolproc, oo(movtstr,keywarn), TRUE },
  { "landscape",        landscape,      0, 0 },
  { "lastfooting",      movt_headproc,  oo(movtstr,lastfooting), rh_lastfooting },
  { "layout",           layout,         0, 0 },
  { "ledgerstyle",      ledgerstyle,    oo(movtstr,ledger), int_u },
  { "leftmargin",       movt_intproc,   oo(movtstr,leftmargin), int_f },
  { "linelength",       movt_intproc,   oo(movtstr,truelinelength), int_rs+int_f },
  { "longrestfont",     movt_fontproc,  oo(movtstr,font_longrest), oo(fontsizestr, fontsize_restct) },
  { "magnification",    opt_intproc,    glob_magnification, int_u+int_f },
  { "maxbeamslope",     maxbeamslope,   0, 0 },
  { "maxvertjustify",   opt_intproc,    glob_maxvertjustify, int_u+int_f },
  { "midichannel",      midichannel,    0, 0 },
  { "midifornotesoff",  opt_boolproc,   glob_midifornotesoff, TRUE },
  { "midistart",        midistart,      0, 0 }, 
  { "miditempo",        playtempo,      oo(movtstr,play_tempo), int_u },
  { "miditranspose",    playtranspose,  0, 0 },
  { "midivolume",       playvolume,     0, 0 },
  { "midkeyspacing",    movt_intproc,   oo(movtstr,keyspacing), int_f },
  { "midtimespacing",   movt_intproc,   oo(movtstr,timespacing), int_f },
  { "musicfont",        textfont,       font_mf, 0 },
  { "nobeamendrests",   movt_cboolproc, oo(movtstr,beamendrests), FALSE },
  { "nocheck",          movt_cboolproc, oo(movtstr,check), FALSE },
  { "nocheckdoublebars",movt_cboolproc, oo(movtstr,checkdoublebars), FALSE },
  { "nokerning",        opt_boolproc,   glob_kerning, FALSE },
  { "nokeywarn",        movt_cboolproc, oo(movtstr,keywarn), FALSE },
  { "nosluroverwarnings", movt_cboolproc, oo(movtstr,tiesoverwarnings), FALSE },
  { "nospreadunderlay", movt_cboolproc, oo(movtstr,spreadunderlay), FALSE },
  { "notespacing",      notespacing,    0, 0 },
  { "notime",           movt_cboolproc, oo(movtstr,showtime), FALSE },
  { "notimebase",       movt_cboolproc, oo(movtstr,showtimebase), FALSE },
  { "notimewarn",       movt_cboolproc, oo(movtstr,timewarn), FALSE },
  { "nounderlayextenders",movt_cboolproc, oo(movtstr,underlayextenders), FALSE }, 
  { "oldbeambreak",     opt_boolproc,   glob_oldbeambreak, TRUE },
  { "oldrestlevel",     opt_boolproc,   glob_oldrestlevel, TRUE },
  { "oldstemlength",    opt_boolproc,   glob_oldstemlength, TRUE },
  { "oldstretchrule",   oldstretchrule, 0, 0 },
  { "overlaydepth",     movt_intproc,   oo(movtstr,overlaydepth), int_f },
  { "overlaysize",      movt_fszproc,   oo(fontsizestr,fontsize_olay), TRUE },
  { "page",             page,           0, 0 },
  { "pagefooting",      movt_headproc,  oo(movtstr,pagefooting), rh_pagefooting },
  { "pageheading",      movt_headproc,  oo(movtstr,pageheading), rh_pageheading },
  { "pagelength",       opt_intproc,    glob_pagelength, int_rs+int_f },
  { "playtempo",        playtempo,      oo(movtstr,play_tempo), int_u },
  { "playtranspose",    playtranspose,  0, 0 },
  { "playvolume",       playvolume,     0, 0 },
  { "pmwversion",       pmwversion,     0, 0 },
  { "printkey",         printkey,       0, 0 }, 
  { "printtime",        printtime,      0, 0 },
  { "psfooting",        movt_headproc,  oo(movtstr,footing), rh_footing+rh_ps },
  { "psheading",        movt_headproc,  oo(movtstr,heading), rh_heading+rh_ps },
  { "pslastfooting",    movt_headproc,  oo(movtstr,lastfooting), rh_lastfooting+rh_ps },
  { "pspagefooting",    movt_headproc,  oo(movtstr,pagefooting), rh_pagefooting+rh_ps },
  { "pspageheading",    movt_headproc,  oo(movtstr,pageheading), rh_pageheading+rh_ps },
  { "pssetup",          pssetup,        0, 0 },
  { "rehearsalmarks",   rehearsalmarks, 0, 0 },
  { "repeatbarfont",    movt_fontproc,  oo(movtstr,font_repeat), oo(fontsizestr, fontsize_repno) },
  { "repeatstyle",      movt_intproc,   oo(movtstr,repeatstyle), int_u },
  { "righttoleft",      opt_boolproc,   glob_righttoleft, TRUE },
  { "selectstaff",      movt_mapproc,   oo(movtstr,staves), 0 },
  { "selectstave",      movt_mapproc,   oo(movtstr,staves), 0 },
  { "selectstaves",     movt_mapproc,   oo(movtstr,staves), 0 },
  { "sheetdepth",       sheetdim,       glob_sheetdepth, int_u+int_f },
  { "sheetsize",        sheetsize,      0, 0 },
  { "sheetwidth",       sheetdim,       glob_sheetwidth, int_u+int_f },
  { "shortenstems",     movt_intproc,   oo(movtstr,shorten), int_u+int_f },
  { "sluroverwarnings", movt_cboolproc, oo(movtstr,tiesoverwarnings), TRUE },
  { "smallcapsize",     movt_intproc,   oo(movtstr,smallcapsize), int_u+int_f },
  { "staffsize",        stavesize,      0, 0 },
  { "staffsizes",       stavesize,      0, 0 },
  { "staffspacing",     stavespacing,   0, 0 },
  { "startbracketbar",  startbracketbar, 0, 0 },
  { "startlinespacing", startlinespacing, 0, 0 },
  { "startnotime",      movt_cboolproc, oo(movtstr,startnotime), TRUE},
  { "stavesize",        stavesize,      0, 0 },
  { "stavesizes",       stavesize,      0, 0 },
  { "stavespacing",     stavespacing,   0, 0 },
  { "stemlengths",      stemlengths,    0, 0 }, 
  { "stemswap",         stemswap,       0, 0 },
  { "stemswaplevel",    stemswaplevel,  0, 0 },
  { "stretchrule",      opt_intproc,    glob_stretchrule, int_u }, 
  { "suspend",          movt_mapproc,   oo(movtstr,suspend), -1 },
  { "systemgap",        movt_intproc,   oo(movtstr,systemgap), int_u+int_f },
  { "textfont",         textfont,       0, 0 },
  { "textsize",         textsizes,      0, 0 },
  { "textsizes",        textsizes,      0, 0 },
  { "thinbracket",      movt_listproc,  oo(movtstr,thinbracketlist), 0 },
  { "time",             timeproc,       0, 0 },
  { "timebase",         movt_cboolproc, oo(movtstr,showtimebase), TRUE },
  { "timefont",         movt_fontproc,  oo(movtstr,font_time),oo(fontsizestr,fontsize_tsfont) },
  { "timewarn",         movt_cboolproc, oo(movtstr,timewarn), TRUE },
  { "topmargin",        movt_intproc,   oo(movtstr,topmargin), int_u+int_f },
  { "transpose",        transpose,      oo(movtstr,transpose), int_s },
  { "transposedacc",    transposedacc,  0, 0 },
  { "transposedkey",    transposedkey,  0, 0 },
  { "trillstring",      trillstring,    0, 0 },
  { "tripletfont",      movt_fontproc,  oo(movtstr,font_triplet),oo(fontsizestr,fontsize_triplet) },
  { "tripletlinewidth", movt_intproc,   oo(movtstr,tripletlinewidth), int_u+int_f },
  { "underlaydepth",    movt_intproc,   oo(movtstr,underlaydepth), int_f },
  { "underlayextenders",movt_cboolproc, oo(movtstr,underlayextenders), TRUE }, 
  { "underlaysize",     movt_fszproc,   oo(fontsizestr,fontsize_ulay), TRUE },
  { "underlaystyle",    movt_intproc,   oo(movtstr,underlaystyle), int_u },
  { "unfinished",       movt_cboolproc, oo(movtstr,unfinished), TRUE },
  { "vertaccsize",      movt_fszproc,   oo(fontsizestr,fontsize_vertacc), FALSE }
};

static int read_headsize = sizeof(read_headlist)/sizeof(dirstr);



/*************************************************
*              Read next heading directive       *
*************************************************/

/* 
Arguments:    none
Returns:      nothing
*/

void 
next_heading(void)
{
dirstr *first, *last;
uschar word[80];

sigch();
if (read_ch == EOF) return;
read_word(word);

/* A null word is either the end of the heading, or an error */

if (word[0] == 0)
  {
  if (read_ch != '[') error_moan(10, "Heading directive");
  return;
  }

/* Look up the word in the list of heading directives and if found, call the
appropriate routine. */

first = read_headlist;
last  = first + read_headsize;

while (last > first)
  {
  int c;
  read_dir = first + (last-first)/2;
  c = Ustrcmp(word, read_dir->name);
  if (c == 0)
    {
    (read_dir->proc)();
    return;
    }
  if (c > 0) first = read_dir + 1; else last = read_dir;
  }
error_moan(19, word);
}

/* End of read2.c */
