/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2019 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: August 2019 */ /* This file contains routines for formatting PMW-specific items of data for printing, often in error messages. */ #include "pmwhdr.h" static uschar *nlsharp[] = { US" C", US"#C", US" D", US"#D", US" E", US" F", US"#F", US" G", US"#G", US" A", US"#A", US" B" }; static uschar *nlflat[] = { US" C", US"$D", US" D", US"$E", US" E", US" F", US"$G", US" G", US"$A", US" A", US"$B", US" B" }; static uschar *octavestring[] = { US"```", US"``", US"`", US"", US"'", US"''", US"'''", US"''''" }; static int notelengths[] = { len_semibreve, len_minim, len_crotchet, len_quaver, len_squaver, len_dsquaver, len_hdsquaver }; static uschar *notenames[] = { US"semibreve", US"minim", US"crotchet", US"quaver", US"semiquaver", US"demisemiquaver", US"hemidemisemiquaver" }; static uschar notefactor[] = { 2, 3, 5, 7, 11 }; /************************************************* * Format a note length * *************************************************/ /* This function is used for "%l" formats in error messages. It turns a note length into text, e.g. "4 crotchets". Arguments: buff where to put the text length the note length Returns: number of bytes placed in the buffer */ static int format_length(uschar *buff, int length) { usint i; int count = 0; int number = -1; uschar *name = NULL; /* Search for a whole number of a particular note type */ for (i = 0; i < sizeof(notelengths)/sizeof(int); i++) { if (length%notelengths[i] == 0) { number = length/notelengths[i]; name = notenames[i]; break; } } /* If integral, it's a simple print */ if (number >= 0) count = sprintf(CS buff, "%d %s%s", number, name, (number==1)?"":"s"); /* Otherwise, compute the fraction of a crotchet */ else { int d = len_crotchet; name = US"of a crotchet"; if (length > len_crotchet) { count += sprintf(CS buff, "%d ", length/len_crotchet); length = length%len_crotchet; name = US"crotchets"; } for (i = 0; i < sizeof(notefactor); i++) { int x = notefactor[i]; while (d%x == 0 && length%x == 0) { d /= x; length /= x; } } count += sprintf(CS buff+count, "%d/%d %s", length, d, name); } return count; } /************************************************* * Format a fixed-point number * *************************************************/ /* All dimensions are held in fixed point with 3 decimal places. Some other numbers are held this way too. This function is used for "%f" formats by format_vsprintf() below. The number is right-justified to the field width if the width is sufficiently large. Arguments: buff where to put the result n the fixed point number width the field width Returns: number of bytes placed in the buffer */ static int format_fixed(uschar *buff, int n, int width) { int spacecount; int count = 0; div_t qr; uschar tbuff[40]; if (n < 0) { tbuff[count++] = '-'; n = -n; } qr = div(n, 1000); count += sprintf(CS tbuff + count, "%d", qr.quot); if (qr.rem) { int r = qr.rem; uschar *z; if (r < 10) z = US".00"; else if (r < 100) z = US".0"; else z = US"."; if ((r%100) == 0) r /= 100; else if ((r%10) == 0) r /= 10; count += sprintf(CS tbuff + count, "%s%d", z, r); } spacecount = width - count; if (spacecount <= 0) spacecount = 0; else Ustrcpy(buff, " "); Ustrcpy(buff+spacecount, tbuff); return count + spacecount; } /************************************************* * Format a key signature * *************************************************/ /* This is used for "%k" format items. Arguments: buff buffer for the result key the key signature Returns: the number of bytes placed in the buffer */ static int format_key(uschar *buff, int key) { uschar *a = US""; uschar *m = US""; uschar *c = (key > 63)? US" cancel" : US""; key &= 63; if (key >= 21) { m = US"m"; key -= 21; } if (key >= 14) { a = US"$"; key -= 14; } else if (key >= 7) { a = US"#"; key -= 7; } return sprintf(CS buff, "%c%s%s%s", "ABCDEFG"[key], a, m, c); } /************************************************* * Format a time signature * *************************************************/ /* This is used for "%t" format items. Arguments: buff buffer for the result time the time signature Returns: the number of bytes placed in the buffer */ static int format_time(uschar *buff, int time) { int count = 0; int m = (time & 0xFF0000) >> 16; int n = (time & 0xFF00) >> 8; int d = time & 255; if (m != 1) count += sprintf(CS buff, "%d*", m); if (d == time_common) return count + sprintf(CS buff+count, "C"); if (d == time_cut) return count + sprintf(CS buff+count, "A"); return count + sprintf(CS buff+count, "%d/%d", n, d); } /************************************************* * Format a stave/page list * *************************************************/ /* This is used for "%L" format items. Arguments: buff buffer for the result p the list pointer Returns: the number of bytes placed in the buffer */ static int format_list(uschar *buff, stave_list *p) { int count = 0; uschar *c = US""; while (p != NULL) { if (p->first == p->last) count += sprintf(CS buff+count, "%s%d", c, p->first); else count += sprintf(CS buff+count, "%s%d-%d", c, p->first, p->last); c = US","; p = p->next; } return count; } /************************************************* * Format a bar number * *************************************************/ /* PMW keeps "true" bar numbers from 1 onwards. However, the existence of the [nocount] directive defines a different set of bar numbers as seen by the user. This procedure prints user bar numbers; for uncounted bars it prints ".". Before calling format_vsprintf with a "%b" item, the caller should set format_movt to point to the relevant movement block. Arguments: buff buffer for the result n the bar number Returns: the number of bytes placed in the buffer */ static int format_barnumber(uschar *buff, int n) { int a, b, p; if (format_movt->barnovector[n+1] <= format_movt->barnovector[n]) return sprintf(CS buff, "%d", n - format_movt->barnovector[n] + format_movt->baroffset); p = n - 1; while (p > 0 && format_movt->barnovector[p] < format_movt->barnovector[p+1]) p--; a = p - format_movt->barnovector[p] + format_movt->baroffset; b = n - p; if (a == 0) b--; if (b == 0) return sprintf(CS buff, "%d", a); else return sprintf(CS buff, "%d.%d", a, b); } /************************************************* * Format a movement phrase * *************************************************/ /* This is used for "%M" format items. If there is only one movement in existence, it returns a null string; otherwise it returns "in movement ". If we can't find the movement (a case that should never happen) return nothing, as that is less misleading! Argument: buffer for the result Returns: the number of bytes placed in the buffer */ static int format_movement(uschar *buff) { int movtnumber = 1; if (movement[2] == NULL) return 0; /* Only movement 1 exists */ while (format_movt != movement[movtnumber]) { if (movement[++movtnumber] == NULL) return 0; } return sprintf(CS buff, " in movement %d", movtnumber); } /************************************************* * Format an absolute pitch * *************************************************/ /* This is used for "%P" format items. It is used when printing out information about a stave. Zero means unset. As for barnumbers, format_movt must point to the relevant movement. This is simply to have a look at the key signature in order to decide whether to show "black" notes as sharps or flats. Arguments: buff buffer for result pitch the pitch Returns: the number of bytes placed in the buffer */ static int format_pitch(uschar *buff, int pitch) { int c; if (pitch) { int octave = (pitch/12); int note = pitch%12; uschar **letters = (main_keysigtable[format_movt->key] >= 0)? nlsharp : nlflat; c = sprintf(CS buff, "%s%s", letters[note], octavestring[octave]); while (c < 5) sprintf(CS buff+c++, " "); } else c = sprintf(CS buff, "unset"); return c; } /************************************************* * Format using a format string (ap arguments) * *************************************************/ /* This function is a private vsprintf() that recognizes a number of additional formatting codes (e.g. "%b" for a bar number). It is mainly used for error and informational output. Arguments: buff where to put the formatted string format the format string ap the va_list variable for any arguments Returns: the number of bytes placed in the buffer */ int format_vsprintf(uschar *buff, const char *format, va_list ap) { uschar *p = buff; while (*format) { int width = 0; BOOL lz = FALSE; if (*format == '%') { if (isdigit(*(++format))) { lz = *format == '0'; do { width = width*10 + *format++ - '0'; } while (isdigit(*format)); } switch (*format) { case 'b': p += format_barnumber(p, va_arg(ap, int)); break; case 'B': p += sprintf(CS p, "%s", va_arg(ap, int)? "true" : "false"); break; case 'c': p += sprintf(CS p, "%c", va_arg(ap, int)); break; case 'd': p += sprintf(CS p, lz? "%0*d":"%*d", width, va_arg(ap, int)); width = 0; break; case 'f': p += format_fixed(p, va_arg(ap, int), width); break; case 'g': p += sprintf(CS p, lz? "%0*g":"%*g", width, va_arg(ap, double)); width = 0; break; case 'k': p += format_key(p, va_arg(ap, int)); break; case 'l': p += format_length(p, va_arg(ap, int)); break; case 'L': p += format_list(p, va_arg(ap, stave_list *)); break; case 'M': p += format_movement(p); break; case 'P': p += format_pitch(p, va_arg(ap, int)); break; case 'p': p += sprintf(CS p, "%p", (void *)va_arg(ap, uschar *)); break; case 's': p += sprintf(CS p, "%s", va_arg(ap, uschar *)); break; case 't': p += format_time(p, va_arg(ap, int)); break; case 'x': p += sprintf(CS p, lz? "%0*x":"%*x", width, va_arg(ap, int)); break; case 'X': p += sprintf(CS p, lz? "%0*X":"%*X", width, va_arg(ap, int)); break; default: *p++ = *format; break; } format++; } else *p++ = *format++; } *p = 0; return p - buff; } /************************************************* * Format using a format string (... arguments) * *************************************************/ /* This function is a private sprintf() that recognizes a number of additional formatting codes (e.g. "%b" for a bar number). It is mainly used for error and informational output. Arguments: buff where to put the formatted string format the format string ... any arguments for the format Returns: the number of bytes placed in the buffer */ int format_sprintf(uschar *buff, const char *format, ...) { va_list ap; va_start(ap, format); return format_vsprintf(buff, format, ap); } /* End of format.c */