#/************************************************* * The PMW Music Typesetter - 2nd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2021 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: January 2021 */ /* This file contains part I of the code for reading in a PMW score file. */ #include "pmwhdr.h" #include "readhdr.h" #include "pagehdr.h" #include "outhdr.h" /************************************************* * Local static variables * *************************************************/ static usint read_staves[STAVE_BITVEC_SIZE]; /* Map of staves actually read */ static int read_prevstave; /* Previous stave number */ static int read_count; static BOOL read_in_quotes; static BOOL read_stavestart; /* True if expecting staff start */ /* A dummy page structure specially for the string escape mechanism which is called to check strings and may need to handle a page number. */ static pagestr dummy_page = { NULL, NULL, NULL, 1, 0, 0, 0 }; /* Default "macro" for the &* replication feature */ static macrostr replicate_macro = { 1, US"&1", { US"" } }; /************************************************* * Expand string with macros substitutions * *************************************************/ /* This is called for input lines, and also for the arguments of nested macro calls. It copies the input string, expanding any macros encountered. If a comment character is encountered, the rest of the line is copied, for error messages, but the returned end value does not include the comment. Arguments: inptr point to the start of the string inend point one past the end of the string outbuffer where to put the expanded line outlen length of the output buffer addNL if TRUE, add a newline character on the end Returns: pointer one past the end of the active part of the expanded string in outbuffer */ static uschar * expand_string(uschar *inptr, uschar *inend, uschar *outbuffer, int outlen, BOOL addNL) { uschar *temp = outbuffer; uschar *comment = NULL; while (inptr < inend) { int ch = *inptr++; /* Keep track of quotes; can't have comments in quotes */ if (ch == '\"') { read_in_quotes = !read_in_quotes; *temp++ = ch; } /* After a comment character, just copy over the rest of the input (for error messages) noting where the comment started. */ else if (!read_in_quotes && ch == '@') { comment = temp; *temp++ = ch; while (inptr < inend) *temp++ = *inptr++; break; } /* Deal with defined insertions, possibly with arguments. Also deal with &*() repetitions, which uses very similar code. */ else if (ch == '&') { if (*inptr == '&') *temp++ = *inptr++; else { macrostr *mm = NULL; BOOL had_semicolon = FALSE; usint count = 1; /* Set up for a macro call. Note that macro names may start with a digit and are case-sensitive, so we can't use next_word(). */ if (isalnum(*inptr)) { int i = 0; tree_node *s; uschar name[WORD_BUFFERSIZE]; name[i++] = *inptr++; while (isalnum(*inptr)) { if (i >= WORD_BUFFERSIZE - 1) error_moan(ERR136, "Macro name", WORD_BUFFERSIZE - 1); /* Hard */ name[i++] = *inptr++; } name[i] = 0; if (*inptr == ';') { inptr++; /* Optional semicolon after name is */ had_semicolon = TRUE; /* skipped, and no args allowed */ } if ((s = Tree_Search(define_tree, name)) != NULL) { mm = (macrostr *)s->data; /* Will be NULL for no replacement */ if (mm == NULL) goto END_MACRO; } else { error_moan(ERR9, name); /* Couldn't find name */ goto END_MACRO; } } /* Set up for a replication call */ else if (*inptr == '*') { int len; if (sscanf((char *)(++inptr), "%u(%n", &count, &len) <= 0) { error_moan(ERR10, "Unsigned number followed by \"(\""); goto END_MACRO; } inptr += len - 1; mm = &replicate_macro; } else { error_moan(ERR8); /* Bad uschar after '&' */ goto END_MACRO; } /* Found a macro or &*. For a macro, mm points to its data, and count is 1. For a replication, mm points to a dummy with 1 empty default argument, count contains the replication count, and we know there is an argument. */ /* Optimize the case when macro is defined with no arguments */ if (mm->argcount == 0) { int k = Ustrlen(mm->text); if (outlen - 2 - (temp - outbuffer) < k + (inend - inptr)) error_moan(ERR88); /* hard error - buffer overflow */ Ustrcpy(temp, mm->text); temp += k; } /* Otherwise we have to process char by char, and read arguments, if any. There need not be; they can all be defaulted. Arguments are read, serially, into argbuff, and then expanded for nested macros into what remains of argbuff. */ else { int i; int argcount = mm->argcount; uschar *args[MAX_MACROARGS]; uschar argbuff[4*READ_BUFFERSIZE]; uschar *ap = argbuff; /* Set up the default arguments */ for (i = 0; i < argcount; i++) args[i] = mm->args[i]; /* Read given arguments, if any, increasing the count if more than the default number. */ if (!had_semicolon && *inptr == '(') { for (i = 0;; i++) { int bracount = 0; BOOL inquotes = FALSE; uschar *ss = ap; if (argcount >= MAX_MACROARGS) error_moan(ERR137, MAX_MACROARGS); /* Hard */ while (++inptr < inend && ((*inptr != ',' && *inptr != ')') || bracount > 0 || inquotes)) { int cch = *inptr; if (cch == '&' && !isalnum(inptr[1]) && inptr[1] != '*') *ap++ = *(++inptr); else { if (cch == '\"') inquotes = !inquotes; if (!inquotes) { if (cch == '(') bracount++; else if (cch == ')') bracount--; } *ap++ = cch; } } if (inptr >= inend) error_moan(ERR99); if (i >= argcount) { args[i] = NULL; argcount++; } if (ap - ss > 0) { *ap++ = 0; args[i] = ss; } if (inptr >= inend || *inptr == ')') { inptr++; break; } } /* Only one argument is currently allowed for a replication. Any others are ignored. */ if (mm == &replicate_macro && argcount > 1) error_moan(ERR134); } /* Process the arguments for nested macro calls */ for (i = 0; i < argcount; i++) { uschar *new_ap; if (args[i] == NULL || Ustrchr(args[i], '&') == NULL) continue; new_ap = expand_string(args[i], args[i] + Ustrlen(args[i]), ap, argbuff + sizeof(argbuff) - ap, FALSE); args[i] = ap; ap = new_ap + 1; /* final zero must remain */ } /* Now copy the replacement, inserting the args. For a replication we repeat many times. For a macro, count is always 1. */ while (count-- > 0) { uschar *pp = mm->text; while (*pp != 0) { if (*pp == '&' && isdigit(pp[1])) { int arg = 0; while (isdigit(*(++pp))) arg = arg*10 + *pp - '0'; if (*pp == ';') pp++; if (--arg < argcount) { uschar *ss = args[arg]; if (ss != NULL) { if (READ_BUFFERSIZE - 2 - (temp - outbuffer) < Ustrlen(ss)) error_moan(ERR88); /* hard */ Ustrcpy(temp, ss); temp += Ustrlen(ss); } } } else { if (temp - outbuffer + 2 >= outlen) error_moan(ERR88); /* hard error - buffer overflow */ *temp++ = *pp++; } } } } /* Macro/replication processing is done */ END_MACRO: continue; } } /* Otherwise it is a normal character */ else *temp++ = ch; } /* For whole lines, keep buffer as a NL-terminated string for debugging */ if (addNL) { temp[0] = '\n'; temp[1] = 0; } else *temp = 0; /* Return the end of the active data */ return comment? comment : temp; } /************************************************* * Read next character * *************************************************/ /* This function updates the global variable read_ch with the next character, including a newline at the end of each line. It deals with macro expansions and preprocessing directives, and it skips comments. Arguments: none Returns: nothing */ void next_ch(void) { for (;;) /* Loop until a character is obtained or very end is reached */ { int len; /* Test for more chars in the current line. If not, return '\n' at end of line (it's not in the data because that may actually end with an '@' for a comment). */ if (read_chptr < read_endptr) { read_ch = *read_chptr++; return; } if (read_chptr++ == read_endptr) { read_ch = '\n'; return; } /* Copy the line just finished into the previous buffer, for use by the error printing routine, unless this line was empty. */ if (this_buffer[0] != 0 && this_buffer[0] != '\n') { uschar *temp = prev_buffer; prev_buffer = this_buffer; this_buffer = temp; } /* Get next logical line, joining together physical lines that end with &&&. At end of file, check for missing "fi"s and deal with included files. */ for (;;) /* Loop for included files */ { if (input_file != NULL) { BOOL line_read = FALSE; uschar *tbuffer = this_buffer; int size = READ_BUFFERSIZE - 4; len = 0; for (;;) /* Loop for concatenated lines */ { if (Ufgets(tbuffer, size, input_file) != NULL) { int tlen = Ustrlen(tbuffer); line_read = TRUE; read_linenumber++; len += tlen; if (tlen < 4 || Ustrcmp(tbuffer + tlen - 4, "&&&\n") != 0) goto PROCESS_LINE; len -= 4; tlen -= 4; tbuffer += tlen; *tbuffer = 0; size -= tlen; } else /* Reached EOF */ { if (line_read) goto PROCESS_LINE; else break; } } /* No lines read */ fclose(input_file); input_file = NULL; } /* Handle reaching the end of an input file */ Ustrcpy(this_buffer, "--- End of file ---"); /* for reflection */ read_chptr = this_buffer + 19; /* just in case */ read_endptr = read_chptr + 1; /* nothing left */ if (read_skipdepth > 0 || read_okdepth > 0) error_moan(ERR18); /* Real end */ if (read_filestackptr <= 0) { read_EOF = TRUE; read_ch = EOF; return; } /* Pop stack at end of included file */ DEBUG(("end of %s: popping include stack\n", main_filename)); store_free(main_filename); main_filename = read_filestack[--read_filestackptr].filename; input_file = read_filestack[read_filestackptr].file; read_linenumber = read_filestack[read_filestackptr].linenumber; read_okdepth = read_filestack[read_filestackptr].okdepth; read_skipdepth = 0; } /* Another line has been read; take care with the final one which may not have a newline on the end. Nor will a final line that is just "&&&\n". */ PROCESS_LINE: read_count += len; read_chptr = this_buffer; read_endptr = this_buffer + len; if (len >= READ_BUFFERSIZE - 5) error_moan(ERR81); /* give-up error */ if (len > 0 && read_endptr[-1] == '\n') read_endptr--; /* Scan the line for comment and defined names, copying into the next buffer. The working buffer is always this_buffer, so that error messages can reflect it. However, if skipping lines, skip this processing too. */ if (read_skipdepth <= 0) { uschar *temp; read_endptr = expand_string(read_chptr, read_endptr, next_buffer, READ_BUFFERSIZE, TRUE); /* Swap this buffer and next buffer, initialize pointer. */ temp = this_buffer; this_buffer = next_buffer; next_buffer = temp; read_chptr = this_buffer; } /* If this buffer begins with '*', it is a pre-processing directive. We process it, and treat as a null line. Set up read_ch before preprocessing, so that code can call normal item reading routines. Clear the in-quotes flag, because a *define can legitimately have unmatched quotes, and no preprocessor directive can in any case have a quoted string that runs over onto the next line. */ while (*read_chptr == ' ' || *read_chptr == '\t') read_chptr++; if (*read_chptr++ == '*') { if (isalpha(read_ch = *read_chptr++)) pre_process(); else error_moan(ERR12); read_chptr = read_endptr; read_in_quotes = FALSE; } else read_chptr = (read_skipdepth > 0)? read_endptr : this_buffer; } } /************************************************* * Read and lowercase next word * *************************************************/ /* Returns: nothing */ void next_word(void) { int i = 0; sigch(); if (isalpha(read_ch)) { do { if (i >= WORD_BUFFERSIZE - 1) error_moan(ERR136, "Word", WORD_BUFFERSIZE - 1); /* Hard */ read_word[i++] = tolower(read_ch); next_ch(); } while (isalnum(read_ch) || read_ch == '_'); } read_word[i] = 0; } /************************************************* * Read plain string * *************************************************/ /* This procedure is used for reading file names and the like in heading directives. These are in quotes, but are not to be interpreted as PMW strings. They are stored in read_word. Returns: TRUE if OK, FALSE if starting quote missing */ BOOL read_plainstring(void) { int i = 0; sigch(); if (read_ch != '\"') return FALSE; next_ch(); while (read_ch != '\"' && read_ch != '\n') { if (i >= WORD_BUFFERSIZE - 1) error_moan(ERR136, "String", WORD_BUFFERSIZE - 1); /* Hard */ read_word[i++] = read_ch; next_ch(); } read_word[i] = 0; if (read_ch == '\"') next_ch(); else error_moan(ERR16, "Terminating quote missing"); return TRUE; } /************************************************* * Read integer or fixed point number * *************************************************/ /* Called when the first character is known to be a digit or a dot. Argument: TRUE to read a fixed point number; FALSE for an integer Returns: the value */ int read_integer(BOOL fixed) { int yield = 0; while (isdigit(read_ch)) { yield = yield*10 + read_ch - '0'; next_ch(); } if (fixed) { yield *= 1000; if (read_ch == '.') { int d = 100; while (next_ch(), isdigit(read_ch)) { yield += (read_ch - '0')*d; d /= 10; } } } return yield; } /************************************************* * Read an expected int or fixed * *************************************************/ /* This is called when the first character hasn't been checked, and an error indication is required if the value is missing. Arguments: yield where to put the value fixed TRUE for a fixed point value, FALSE for an integer allowsign TRUE if a sign is permitted Returns: TRUE if OK, FALSE on error */ BOOL read_expect_integer(int *yield, int fixed, int allowsign) { int sign = 1; sigch(); if (allowsign) { if (read_ch == '+') next_ch(); else if (read_ch == '-') { sign = -1; next_ch(); } } if (!isdigit(read_ch)) { error_moan(ERR10, allowsign? "Number" : "Unsigned number"); return FALSE; } *yield = sign * read_integer(fixed); return TRUE; } /************************************************* * Read an expected movement dimension * *************************************************/ /* This function is called after /u, /d, /l, or /r has been read. Arguments: none Returns: the value, or zero after an error */ int read_movevalue(void) { int x; next_ch(); return (read_expect_integer(&x, TRUE, FALSE))? x : 0; } /************************************************* * Read key signature * *************************************************/ /* Arguments: none Returns: the key signature (C major after an error) */ int read_key(void) { int key = C_key; sigch(); read_ch = tolower(read_ch); if ('a' <= read_ch && read_ch <= 'g') { key = read_ch - 'a'; next_ch(); if (read_ch == '#') { key += 7; next_ch(); } else if (read_ch == '$') { key += 14; next_ch(); } if (tolower(read_ch) == 'm') { key += 21; next_ch(); } if (main_keysigtable[key] == -100) { key = C_key; error_moan(ERR24); } } else if (read_ch == 'n') { key = N_key; next_ch(); } else if (read_ch == 'x') { int n; next_ch(); if (!read_expect_integer(&n, FALSE, FALSE) || n == 0 || n > MAX_XKEYS) error_moan(ERR144, MAX_XKEYS); else key = X_key + n - 1; } else error_moan(ERR10, "Key signature"); return key; } /************************************************* * Double/halve time signature * *************************************************/ /* The global variables main_notenum and main_noteden contain the numerator and denominator of any note scaling (doublenotes, halvenotes) that was set up in the movement's header. This affects time signatures. The stave directives [doublenotes] and [halvenotes] do not affect time signatures. Argument: the time signature Returns: the scaled time signature */ int read_scaletime(int ts) { int m, n, d; if (main_notenum == 1 && main_noteden == 1) return ts; m = (ts & 0xffff0000) >> 16; /* multiplier */ n = (ts & 0x0000ff00) >> 8; /* numerator */ d = (ts & 0x000000ff); /* denominator */ if (d == time_common || d == time_cut) { m *= main_notenum; if (main_noteden > 1) { if (m%main_noteden == 0) m /= main_noteden; else error_moan(ERR102); } return (m << 16) | d; } d *= main_noteden; if (d % main_notenum == 0) d /= main_notenum; else n *= main_notenum; return (m << 16) + (n << 8) + d; } /************************************************* * Read time signature * *************************************************/ /* Return zero on any error. A time signature can be of the form m*n/d. Arguments: none Returns: the packed up time signature */ int read_time(void) { BOOL gotnum = FALSE; int m, n, d; sigch(); if (isdigit(read_ch)) { (void)read_expect_integer(&m, FALSE, FALSE); sigch(); if (read_ch == '*') next_ch(); else { n = m; m = 1; gotnum = TRUE; } } else m = 1; if (!gotnum) { read_ch = tolower(read_ch); if (read_ch == 'a' || read_ch == 'c') { int type = (read_ch == 'c')? time_common : time_cut; next_ch(); return read_scaletime((m << 16) | type); } if (!read_expect_integer(&n, FALSE, FALSE)) return 0; } sigch(); if (read_ch != '/') { error_moan(ERR10, "\"/\""); return 0; } next_ch(); if (!read_expect_integer(&d, FALSE, FALSE)) return 0; if (d < 1 || d > 64 || d != (d & (-d))) error_moan(ERR87); /* hard error */ return read_scaletime((m << 16) | (n << 8) | d); } /************************************************* * Compute barlength from time signature * *************************************************/ /* Argument: the time signature Returns: the bar length */ int read_compute_barlength(int ts) { int m = (ts >> 16) & 255; int n = (ts >> 8) & 255; int d = ts & 255; if (d == time_common) n = d = 4; else if (d == time_cut) n = d = 2; return n * m * (len_semibreve/d); } /************************************************* * Get MIDI number from string * *************************************************/ /* This function scans a list of MIDI voice or percussion names to find the number for a given name. If we are not generating MIDI (midi_filename is NULL), there is no search, but we do not give an error. Just return zero. Arguments: list the list of MIDI names string the string to search for text text for use in the error message Returns: the MIDI number */ int read_getmidinumber(uschar *list, uschar *string, uschar *text) { int yield = -1; if (list != NULL) while (*list) { int len = Ustrlen(list); if (Ustrcmp(list, string) == 0) { yield = list[len+1]; break; } list += len + 2; } if (yield < 0) { if (midi_filename != NULL) error_moan(ERR110, text, string); yield = 0; } return yield; } /************************************************* * Start of movement initialization * *************************************************/ /* This function resets values that must be reset at the start of every movement. Other per-movement data is initialized to default values (the init_movtstr variable) for the first movement, then copied to subsequent movements. Argument: option for type of movement (same page, new page, etc) Returns: nothing */ static void init_movt(int opt) { int i; curmovt->stavetable = store_Xget((MAX_STAVE+1) * sizeof(stavestr *)); stavetable = curmovt->stavetable; for (i = 0; i <= MAX_STAVE; i++) stavetable[i] = NULL; curmovt->number = main_lastmovement; curmovt->barcount = 0; curmovt->baroffset = 0; curmovt->barnovector = NULL; /* Got when first stave encountered */ curmovt->heading = curmovt->footing = NULL; curmovt->key = C_key; /* C major */ curmovt->laststave = -1; curmovt->movt_opt = opt; curmovt->notespacing = store_Xget(8*sizeof(int)); memcpy(curmovt->notespacing, main_notespacing, 8*sizeof(int)); curmovt->layout = NULL; curmovt->play_changes = NULL; curmovt->play_tempo_changes = NULL; curmovt->posvector = NULL; curmovt->showtime = TRUE; curmovt->startbracketbar = 0; curmovt->startnotime = FALSE; memcpy(curmovt->staves, main_staves, STAVE_BITVEC_SIZE*sizeof(int)); curmovt->time = 0x00010404; /* 1*4/4 */ curmovt->transpose = main_transpose; curmovt->unfinished = FALSE; read_copied_fontsizestr = FALSE; read_headcount = 1; /* Very first size for first movement only */ read_headmap = 0; read_lastplaychange = &(curmovt->play_changes); main_notenum = main_noteden = 1; read_prevstave = 0; /* previous stave (fudged when stave 0 read) */ mac_initstave(curmovt->suspend, 0); mac_initstave(read_staves, 0); error_skip = skip_EOL; /* Where to skip for some errors */ /* In case there are transposed text strings in the headers, ensure the appropriate stave data is fudged. */ stave_key = curmovt->key; stave_transpose = curmovt->transpose; stave_key_tp = transpose_key(stave_key, stave_transpose); } /************************************************* * Handle end of movement * *************************************************/ /* Called to tidy up at the end of each movement. Arguments: none Returns: nothing */ static void read_endmovement(void) { int i; for (i = 0; i < STAVE_BITVEC_SIZE; i++) curmovt->staves[i] &= read_staves[i]; /* Disable those not read */ /* If there was a playtempo directive, adjust the bar numbers so that they are internal rather than external values */ if (curmovt->play_tempo_changes != NULL) { int *p = curmovt->play_tempo_changes; while (*p < BIGNUMBER) { uschar *bv = curmovt->barnovector; int n = (*p)/1000 - curmovt->baroffset; int f = (*p)%1000; if ((f%10) == 0) { f = ((f%100) == 0)? (f/100) : (f/10); } if (n <= 0) n = 1; else { for (i = 1; i <= n; i++) if (bv[i] < bv[i+1]) n++; } *p = n + f; p += 2; } } } /************************************************* * Top-level reading control * *************************************************/ /* Called from the main control function. This function reads the input stream, alternating between heading directives and music staves for the different movements that are present. Arguments: none Returns: nothing */ void read_go(void) { DEBUG(("read_go() start\n")); /* Deal with reading the heading part of a movement. We jump back here when a new movement is encountered. */ READ_HEADING: for (;;) { next_heading(); /* The appearance of '[' indicates the start of the stave data. Set up things that can't be done till now. */ if (read_ch == '[') { int j; curmovt->barnovector = store_Xget(curmovt->maxbarcount+1); for (j = 0; j <= curmovt->maxbarcount; j++) curmovt->barnovector[j] = 0; read_stavestart = TRUE; error_skip = skip_KET; /* Where to skip for some errors */ break; } /* End of file before any staff data has been read. */ else if (read_ch == EOF) break; } /* Loop to deal with reading the stave part of a movement. */ for (;;) { sigch(); if (read_ch == EOF) break; /* Deal with starting a new stave or movement. */ if (read_stavestart) { if (read_ch != '[') { error_moan(ERR29); break; } next_ch(); next_word(); /* It's another stave. Check that a valid number is given. If so, set up the index for its bars and get ready to read the rest of the data. */ if (Ustrcmp(read_word, "stave") == 0 || Ustrcmp(read_word, "staff") == 0) { int k; if (!read_expect_integer(&curstave, FALSE, FALSE)) { error_moan(ERR30, "Stave number"); /* Failed */ break; } /* Error if number too big or stave supplied twice */ if (curstave > MAX_STAVE) { error_moan(ERR22, MAX_STAVE+1); break; } if (stavetable[curstave] != NULL) { error_moan(ERR31, curstave); break; } /* Staves must be in order; missing staves are inserted as empty, except for stave 0, which is just omitted. This is historical really. The rest of PMW assumes contiguous staves, and to allow some to be missing, it is less boat-rocking to do it this way. */ if (curstave == 0) read_prevstave = -1; for (k = read_prevstave+1; k <= curstave; k++) { int j; mac_setstave(read_staves, (usint)k); if (k > curmovt->laststave) curmovt->laststave = k; /* Set up the data structure for the stave */ stavehead = stavetable[k] = store_Xget(sizeof(stavestr)); stavehead->stave_name = NULL; stavehead->lastbar = 0; stavehead->toppitch = -1; stavehead->botpitch = 9999; stavehead->totalpitch = 0; stavehead->notecount = 0; stavehead->omitempty = FALSE; stavehead->stavelines = 5; stavehead->barlinestyle = 255; /* "unset" */ barvector = stavehead->barindex = store_Xget((curmovt->maxbarcount+1) * sizeof(bstr *)); for (j = 0; j <= curmovt->maxbarcount; j++) barvector[j] = NULL; } read_prevstave = curstave; /* Initialize miscellaneous variables */ stave_accritvalue = 3; stave_couplestate = stave_octave = 0; stave_notes = TRUE; stave_noteflags = (curstave == 0)? nf_hidden : 0; stave_notenum = main_notenum; stave_noteden = main_noteden; stave_matchnum = 0; /* indicates "unset" */ stave_totalnocount = stave_slurcount = stave_hairpinbegun = stave_hairpiny = 0; stave_stemforce = stave_ties = stave_textflags = 0; stave_textabsolute = 0; stave_accentflags = stave_restlevel = stave_stemlength = 0; stave_pletflags = stave_plety = 0; stave_ornament = -1; stave_printpitch = 0; stave_stemflag = nf_stem; stave_pendulay = NULL; stave_hairpinflags = hp_below; stave_hairpinwidth = curmovt->hairpinwidth; stave_clef = clef_treble; stave_clef_octave = 0; stave_barlinestyle = curmovt->barlinestyle; stave_transposedaccforce = main_transposedaccforce; stave_transpose = curmovt->transpose; stave_key = curmovt->key; /* Original key */ /* We have to call transpose_key in order to get the value for the letter transformation set, even if there is a forced key signature */ stave_key_tp = transpose_key(stave_key, stave_transpose); stave_requiredbarlength = read_compute_barlength(curmovt->time); stave_beaming = stave_lastwastied = stave_lastwasdouble = FALSE; stave_laststemup = TRUE; stave_smove = stave_octave = 0; stave_minpitch = 999; stave_maxpitch = stave_pitchtotal = stave_pitchcount = 0; stave_lastbasenoteptr = NULL; stave_fbfont = stave_ulfont = stave_olfont = font_rm; stave_textfont = font_it; stave_textsize = 0; stave_olsize = ff_offset_olay; stave_ulsize = ff_offset_ulay; stave_fbsize = ff_offset_fbass; stave_suspended = mac_teststave(curmovt->suspend, curstave); stave_stemswaplevel = curmovt->stemswaplevel; read_prev_had_dbar = read_prev_had_ibar = FALSE; read_prev_barlinestyle = stave_barlinestyle; /* Set the bar number, and enter the normal reading state, with a pre-fixed "name" directive if necessary. */ stave_barnumber = 1; read_stavestart = read_endstave = FALSE; sigch(); if (read_ch == '\"' || (read_ch == 'd' && Ustrncmp(read_chptr, "raw ", 4) == 0)) { Ustrcpy(read_word, "name"); read_stavedir = TRUE; } read_chptr--; read_ch = '['; } /* It's another movement. Check for options, set up a new movement control block, and change state. */ else if (Ustrcmp(read_word, "newmovement") == 0) { int opt = movt_default; int ph = 0; int lf = 0; movtstr *newcurmovt; if (main_lastmovement >= main_max_movements) { error_moan(ERR120, main_max_movements); break; } /* disaster */ sigch(); while (read_ch != ']') { next_word(); sigch(); if (Ustrcmp(read_word, "thispage") == 0) opt = movt_thispage; else if (Ustrcmp(read_word, "thisline") == 0) opt = movt_thisline; else if (Ustrcmp(read_word, "newpage") == 0) opt = movt_newpage; else if (Ustrcmp(read_word, "nopageheading") == 0) ph = movt_nopageheading; else if (Ustrcmp(read_word, "uselastfooting") == 0) lf = movt_uselastfooting; else error_moan(ERR10, "\"thispage\", \"thisline\", \"newpage\", \"nopageheading\", or \"uselastfooting\""); } next_ch(); read_endmovement(); /* Tidy up */ newcurmovt = movement[++main_lastmovement] = store_Xget(sizeof(movtstr)); *newcurmovt = *curmovt; format_movt = curmovt = newcurmovt; init_movt(opt | ph | lf); goto READ_HEADING; /* Jump back to start of function */ } /* It's a disastrous error */ else { error_moan(ERR29); break; } } /* Otherwise we are in the middle of reading a stave's data. Read another bar. FALSE is yielded at the end of the staff. */ else { int oldnocount = stave_totalnocount; BOOL more = read_bar(); int nocount = (stave_totalnocount > oldnocount)? 1:0; /* If there was a repeat count in the bar, set up additional pointers to the repeated part of the bar and handle nocounts. */ while (stave_barrepeatcount--) { if (++stave_barnumber > curmovt->maxbarcount) { error_moan(ERR36, curmovt->maxbarcount); more = FALSE; break; } if (stave_totalnocount > (curmovt->barnovector)[stave_barnumber]) (curmovt->barnovector)[stave_barnumber] = stave_totalnocount; stave_totalnocount += nocount; (stavehead->barindex)[stave_barnumber] = stave_barrepeatptr; } /* Increase the bar number and set the high water mark if there is more or if this is a non-empty end-of-stave bar. */ if (more || (stavehead->barindex)[stave_barnumber] != NULL) stavehead->lastbar = stave_barnumber++; /* Deal with reaching the end of the stave */ if (!more) { if (stavehead->lastbar > curmovt->barcount) curmovt->barcount = stavehead->lastbar; if (stave_pitchcount) { stavehead->toppitch = stave_maxpitch; stavehead->botpitch = stave_minpitch; stavehead->totalpitch = stave_pitchtotal; stavehead->notecount = stave_pitchcount; } else stavehead->toppitch = stavehead->botpitch = 0; if (stave_totalnocount > curmovt->totalnocount) curmovt->totalnocount = stave_totalnocount; if (stave_pendulay != NULL) { error_moan(ERR65, curstave); /* Give warning */ while (stave_pendulay != NULL) { ulaypend *next = stave_pendulay->next; store_free(stave_pendulay); stave_pendulay = next; } } read_stavestart = TRUE; /* Flag to expect a new stave or movement */ } } } read_endmovement(); /* Terminate the final movement */ store_free(next_buffer); store_free(this_buffer); store_free(prev_buffer); store_free(read_word); if (input_file != NULL) fclose(input_file); while (read_filestackptr > 0) fclose(read_filestack[--read_filestackptr].file); /* Warn if the format word was never tested. By giving the error after resetting reading_input, it won't try to reflect the input line. */ reading_input = FALSE; if (main_format[0] != 0 && !main_format_tested) error_moan(ERR104, main_format); DEBUG(("read_go() end\n")); } /************************************************* * Set up for reading routines * *************************************************/ /* This function is called at the start of reading. Arguments: none Returns: nothing */ void read_start(void) { int i; /* Stack for included files */ read_filestack = store_Xget(MAX_INCLUDE*sizeof(filestr)); /* Set up buffers for line reading */ read_word = store_Xget(WORD_BUFFERSIZE); next_buffer = store_Xget(READ_BUFFERSIZE); this_buffer = store_Xget(READ_BUFFERSIZE); prev_buffer = store_Xget(READ_BUFFERSIZE); this_buffer[0] = prev_buffer[0] = 0; /* Variables for controlling reading of the file */ read_filestackptr = 0; read_linenumber = read_count = error_count = main_rc = 0; read_skipdepth = read_okdepth = 0; read_EOF = FALSE; read_stavedir = FALSE; read_in_quotes = FALSE; read_chptr = this_buffer + 1; read_endptr = this_buffer; read_ch = ' '; /* Default start of movement note spacing */ memcpy(main_notespacing, init_notespacing, 8*sizeof(int)); /* All custom keys default to no key signature. */ memset(main_xkeys, 0, sizeof(main_xkeys)); memset(main_xkeyorder, 255, sizeof(main_xkeyorder)); /* Initialize movement table for first movement */ main_lastmovement = 1; for (i = 0; i <= main_max_movements; i++) movement[i] = NULL; curmovt = movement[1] = store_Xget(sizeof(movtstr)); *curmovt = init_movtstr; /* Default settings */ init_movt(movt_default); /* Reset things that need resetting */ read_headcount = 0; /* First size for movement 1 only */ /* Set available fonts to those read from the font list file, and set the default font allocations. */ font_count = font_basecount; font_table[font_rm] = font_search(US"Times-Roman"); font_table[font_it] = font_search(US"Times-Italic"); font_table[font_bf] = font_search(US"Times-Bold"); font_table[font_bi] = font_search(US"Times-BoldItalic"); font_table[font_sy] = font_search(US"Symbol"); font_table[font_mf] = font_search(main_musicchoice); font_table[font_mu] = font_search(main_musicchoice); for (i = font_xx; i < font_tablen; i++) font_table[i] = font_search(US"Times-Roman"); if (font_table[font_rm] < 0 || font_table[font_mf] < 0) error_moan(ERR25); /* Hard */ /* Point current page at a dummy (relevant for string escape checking) */ curpage = &dummy_page; /* Miscellaneous start-of-file initialization */ baraccs = store_Xget(baraccs_len); baraccs_tp = store_Xget(baraccs_len); stave_tiedata = store_Xget(MAX_CHORDSIZE * sizeof(tiedata)); stave_beamstack = store_Xget(beamstacksize); stave_stemstack = store_Xget(stemstacksize); beam_overbeam = FALSE; /* For misc_nextnote() while reading */ page_postable = store_Xget(MAX_POSTABLESIZE * sizeof(workposstr)); define_tree = draw_tree = draw_variable_tree = NULL; draw_thickness = 500; draw_nextvariable = 0; opt_landscape = opt_oldbeambreak = opt_oldrestlevel = opt_oldstemlength = FALSE; main_firstpage = main_pageinc = 1; main_format_tested = FALSE; main_htypes = NULL; main_kerning = TRUE; main_magnification = 1000; main_maxvertjustify = 60000; main_pageanchor = NULL; main_printkey = NULL; main_printtime = NULL; main_pssetup = NULL; main_sheetheight = 842000; main_sheetwidth = 595000; opt_sheetsize = sheet_A4; opt_stretchrule = 2; /* The latest rule */ main_transposedaccforce = TRUE; main_transposedkeys = NULL; main_truepagelength = 720000; draw_lgx = draw_lgy = 0; out_depthvector = store_Xget((MAX_STAVE+1) * sizeof(int *)); font_reset(); /* To set no transformation */ font_xstretch = 0; /* No justification */ /* Finally, enter the reading state. This affects the form of error messages. */ reading_input = TRUE; } /* End of read1.c */