/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: August 2020 */ /* This file contains initializing code, including the main program, which is the entry point to PMW. This comment is historical, left here for nostalgia purposes only: ------------------------------------------------------------------------------ PMW can be run in an windowing environment, or as a command-line program. The windowing environments are of course system-specific, but have the characteristic that they are event-driven. Thus in such an environment we hand over control to a system routine, and only get it back (if at all) when the program is finishing. For the moment, we are thinking only of the RISC OS environment, but writing as flexibly as possible, so that future ports are easier. ------------------------------------------------------------------------------ The future has arrived. This port of PMW for Unix-like systems runs only as a single command, processing a single input file (though that may include other files). Much of the old apparatus for the previous windowing version has been tidied away, but there are still a few quirks in the way the code works that hark back to the original, event-driven implementation. */ #include "rdargs.h" #include "pmwhdr.h" #include "outhdr.h" /* Keywords for the command line. If you increase the number of keys, make sure that the keyoffset field in rdargs.c is big enough. */ static const char *arg_pattern = "," "a4ona3/s," "a4sideways/s," "a5ona4/s," "c/k/n," "debug/s," "dbl=drawbarlines/s," "dsl=drawstavelines=drawstafflines/n=3," "dsb/k," "dtp/k/n," "duplex/s," "em=errormaximum/n," "eps/s," "F/k," "f/k," "H/k," "-help=help/s," "incPMWfont=incpmwfont=ipf=includefont/s," "MF/k," "MP/k," "MV/k," "manualfeed/s," "midi/k," "mb=midibars/k," "mm=midimovement/k/n," "norc=nopmwrc/s," "nr=norepeats=norepeat/s," "nw=nowidechars/s," "o/k," "p/k," "pamphlet/s," "printadjust/k/2," "printgutter/k," "printscale/k," "printside/k/n," "reverse/s," "s/k," "t/k/n," "tumble/s," "-version=V/s," "v/s"; /* Offsets for command line keys */ enum { arg_aa_input, /* The only unkeyed possibility */ arg_a4ona3, arg_a4sideways, arg_a5ona4, arg_c, arg_debug, arg_drawbarlines, arg_drawstavelines, arg_dsb, arg_dtp, arg_duplex, arg_em, arg_eps, arg_F, arg_f, arg_H, arg_help, arg_incPMWfont, arg_MF, arg_MP, arg_MV, arg_manualfeed, arg_midi, arg_midibars, arg_midimovement, arg_norc, arg_norepeats, arg_nowidechars, arg_o, arg_p, arg_pamphlet, arg_printadjustx, arg_printadjusty, arg_printgutter, arg_printscale, arg_printside, arg_reverse, arg_s, arg_t, arg_tumble, arg_V, arg_v }; /* Parameters for debugging with -dsb option */ static int dsb_bar = -1; static int dsb_movement = -1; static int dsb_stave = -1; /* Vector for modified command line options */ static char **newargv; /************************************************* * Given help on command syntax * *************************************************/ static void givehelp(void) { printf("\nPMW version %s\n%s\n", version_string, copyright); printf("\n OPTIONS\n\n"); printf("-a4ona3 print A4 images 2-up on A3\n"); printf("-a5ona4 print A5 images 2-up on A4\n"); printf("-a4sideways assume A4 paper fed sideways\n"); printf("-c set number of copies\n"); printf("-debug write debugging info to stderr\n"); printf("-dbl synonym for -drawbarlines\n"); printf("-drawbarlines don't use characters for bar lines\n"); printf("-drawstavelines [] don't use characters for stave lines\n"); printf("-dsb ,, write debugging bar data (movement, stave, bar) \n"); printf("-dsl [] synonym for -drawstavelines\n"); printf("-dtp write debugging position data (-1 for all bars)\n"); printf("-duplex set duplex printing in the PostScript\n"); printf("-em synonym for -errormaximum\n"); printf("-eps output encapsulated PostScript\n"); printf("-errormaximum set maximum number of errors (for testing)\n"); printf("-F specify fontmetrics and/or .utr directories\n"); printf("-f specify format name\n"); printf("-H specify PostScript header file\n"); printf("-help output this information\n"); printf("-incPMWfont include PMW font in the output\n"); printf("-ipf synonym for -incPMWfont\n"); printf("-MF specify PostScript music fonts directories\n"); printf("-MP specify MIDIperc file\n"); printf("-MV specify MIDIvoices file\n"); printf("-manualfeed set manualfeed in the PostScript\n"); printf("-mb synonym for -midibars\n"); printf("-midi specify MIDI output file\n"); printf("-midibars limit MIDI output to given bar range\n"); printf("-midimovement specifies movement for MIDI output\n"); printf("-mm synonym for -midimovement\n"); #ifndef NO_PMWRC printf("-norc or -nopmwrc don't read .pmwrc (must be first option)\n"); #endif printf("-norepeats do not play repeats in MIDI output\n"); printf("-nowidechars don't use 100-point stave chars\n"); printf("-nr synonym for -norepeats\n"); printf("-nw synonym for -nowidechars\n"); printf("-o specify output file ('-' for stdout)\n"); printf("-p select pages\n"); printf("-pamphlet print pages in pamphlet order\n"); printf("-printadjust move on page by (x,y)\n"); printf("-printgutter move recto/verso pages by x/-x\n"); printf("-printscale scale the image by n\n"); printf("-printside print only odd or even sides\n"); printf("-reverse output pages in reverse order\n"); printf("-s select staves\n"); printf("-t set transposition\n"); printf("-tumble set tumble for duplex printing\n"); printf("-V output PMW version number\n"); printf("-v output verification information\n"); printf("\nDefault output is .ps when a file name is given.\n"); printf("Default output is stdout if no file name is given.\n"); printf("\n EXAMPLES\n\n"); printf("pmw myscore\n"); printf("pmw -s 1,2-4 -p 3,6-10,11 -f small -c 2 k491.pmw\n"); printf("pmw -pamphlet -a5ona4 scorefile\n"); printf("pmw -s 1 -midi zz.mid -mm 2 -mb 10-20 sonata\n"); } /************************************************* * Print routine for info display * *************************************************/ /* This could just be replaced by fprintf() to stderr nowadays, but we keep the separate function just in case in the future we want do so something else with all this output. The function is global because it is also called from setdraw.c to show the contents of the draw stack. Arguments: format a format ... data for the format Returns: nothing */ void info_printf(const char *format, ...) { uschar buff[256]; va_list ap; va_start(ap, format); format_vsprintf(buff, format, ap); fprintf(stderr, "%s", CS buff); va_end(ap); } /************************************************* * Display information about music * *************************************************/ /* This function is called after pagination if the -v option is present. Arguments: none Returns: nothing */ static void display_info(void) { pagestr *p = main_pageanchor; int movt; int laststave = -1; int toppitch[MAX_STAVE+1]; int botpitch[MAX_STAVE+1]; int totalpitch[MAX_STAVE+1]; int notecount[MAX_STAVE+1]; info_printf("Data store used = "); if (main_storetotal < 10000) info_printf("%d", main_storetotal); else info_printf("%dK", main_storetotal/1024); info_printf(" (stave data "); if (main_storestaves < 10000) info_printf("%d", main_storestaves); else info_printf("%dK", main_storestaves/1024); info_printf(")\n"); /* Display information about the staves in each movement */ for (movt = 1; movt <= main_lastmovement; movt++) { int stave; movtstr *m = movement[movt]; info_printf("\nMOVEMENT %d\n\n", movt); for (stave = 0; stave <= m->laststave; stave++) { stavestr *s = (m->stavetable)[stave]; if (s == NULL) continue; /* skips stave 0 if not there */ info_printf("Stave %2d: ", stave); if (m->totalnocount == 0) info_printf("%d bar%s", s->lastbar, (s->lastbar == 1)? "":"s"); else info_printf("%d(+%d) bars", s->lastbar - m->totalnocount, m->totalnocount); if (stave > laststave) { laststave = stave; toppitch[stave] = -1; botpitch[stave] = 9999; notecount[stave] = totalpitch[stave] = 0; } if (s->notecount > 0) { info_printf(";%s range %P to %P average %P", (s->lastbar == 1)? " ":"", s->botpitch, s->toppitch, s->totalpitch/s->notecount); if (s->toppitch > toppitch[stave]) toppitch[stave] = s->toppitch; if (s->botpitch < botpitch[stave]) botpitch[stave] = s->botpitch; totalpitch[stave] += s->totalpitch; notecount[stave] += s->notecount; } info_printf("\n"); } } /* If there is more than one movement, display overall information for each stave. */ if (main_lastmovement > 1) { int stave; info_printf("\nOVERALL\n\n"); for (stave = 1; stave <= laststave; stave++) { info_printf("Stave %2d: ", stave); if (notecount[stave] > 0) info_printf("range %P to %P average %P", botpitch[stave], toppitch[stave], totalpitch[stave]/notecount[stave]); info_printf("\n"); } } /* Now display information about the page layout */ if (p != NULL) info_printf("\nPAGE LAYOUT\n\n"); while (p != NULL) { int count = 14; sysblock *s = p->sysblocks; info_printf("Page %d bars: ", p->number); while (s != NULL) { if (s->type == sh_system) { format_movt = s->movt; if (count > 65) { info_printf("\n "); count = 1; } info_printf("%b-%b%s ", s->barstart, s->barend, (s->flags & sysblock_stretch)? "":"*"); count += 6; if (s->overrun < 30) { info_printf("(%d) ", s->overrun); count += 5; } } s = s->next; } info_printf("\n Space left on page = %f", p->spaceleft); if (p->overrun > 0 && p->overrun < 100000) info_printf(" Overrun = %f", p->overrun); info_printf("\n"); p = p->next; } } /************************************************* * Convert string to stave map * *************************************************/ /* The turns strings like "1,3,4-6,10" into a bitmap. For some reason gcc gives a weird warning if ss is used directly with strtol, even with the right casts. That's why I use another variable of type char *. Arguments: ss the string endptr where to return a pointer to the char after the last used map pointer to bitmap, held as unsigned ints Returns: nothing */ static void init_strtomap(uschar *ss, uschar **endptr, usint *map) { long int i; char *sss = (char *)ss; *endptr = ss; mac_initstave(map, 0); while (isdigit(*sss)) { long int s = strtol(sss, &sss, 0); long int t = s; if (*sss == '-') { sss++; t = strtol(sss, &sss, 0); } if (t < s || t > MAX_STAVE) return; /* Not reached end will give error */ for (i = s; i <= t; i++) mac_setstave(map, i); while (*sss == ',' || *sss == ' ') sss++; } *endptr = US sss; } /************************************************* * Decode command line * *************************************************/ /* -V and -help act immediately; otherwise the values from the command line options are place in appropriate global variables. Arguments: argc the (possibly modified) command line argc argv the (possibly modified) command line argv Returns: nothing */ static void decode_command(int argc, char **argv) { arg_result results[80]; int rc = rdargs(argc, argv, arg_pattern, results); if (rc != 0) error_moan(ERR0, results[0].text, results[1].text); /* Hard */ /* -norc is invalid here; give an explanation. */ if (results[arg_norc].number != 0) error_moan(ERR153); /* Deal with -V */ if (results[arg_V].number != 0) { printf("PMW version %s\n%s\n", version_string, copyright); exit(EXIT_SUCCESS); } /* Deal with -help */ if (results[arg_help].number != 0) { givehelp(); exit(EXIT_SUCCESS); } /* Deal with verifying and debugging */ if (results[arg_v].number != 0) verify = TRUE; if (results[arg_debug].number != 0) { debug_file = stderr; debugging = TRUE; debug_printf("PMW run started\n"); } if (results[arg_dsb].text != NULL) { if (strspn(results[arg_dsb].text, "0123456789,") != strlen(results[arg_dsb].text)) error_moan(ERR77); /* Hard */ switch (sscanf(results[arg_dsb].text, "%d,%d,%d", &dsb_movement, &dsb_stave, &dsb_bar)) { case 1: /* One value is a bar number */ dsb_bar = dsb_movement; dsb_movement = dsb_stave = 1; break; case 2: /* Two values are stave, bar */ dsb_bar = dsb_stave; dsb_stave = dsb_movement; dsb_movement = 1; break; case 3: /* Three values are movt, stave, bar */ break; default: error_moan(ERR77); /* Hard */ break; } debug_file = stderr; } if (results[arg_dtp].presence != arg_present_not) { debug_file = stderr; main_tracepos = results[arg_dtp].number; } /* Deal with -from and -o */ if (results[arg_aa_input].text != NULL) arg_from_name = US results[arg_aa_input].text; if (results[arg_o].text != NULL) arg_to_name = US results[arg_o].text; /* Deal with overriding music fonts, fontmetrics, and psheader, MIDIperc, and MIDIvoices files */ if (results[arg_F].text != NULL) font_data_extra = US results[arg_F].text; if (results[arg_H].text != NULL) ps_header = US results[arg_H].text; if (results[arg_MF].text != NULL) font_music_extra = US results[arg_MF].text; if (results[arg_MP].text != NULL) midi_perc = US results[arg_MP].text; if (results[arg_MV].text != NULL) midi_voices = US results[arg_MV].text; /* Deal with MIDI output */ if (results[arg_midi].text != NULL) midi_filename = US results[arg_midi].text; if (results[arg_midibars].text != NULL) { uschar *endptr; play_startbar = Ustrtoul(results[arg_midibars].text, &endptr, 10); if (*endptr == 0) play_endbar = play_startbar; else { if (*endptr++ != '-') error_moan(ERR123); /* Hard error */ play_endbar = Ustrtoul(endptr, &endptr, 10); if (*endptr != 0) error_moan(ERR123); /* Hard error */ } } if (results[arg_midimovement].presence != arg_present_not) play_movt_number = results[arg_midimovement].number; /* Error limit is adjustable, mainly for testing */ if (results[arg_em].presence != arg_present_not) error_maximum = results[arg_em].number; /* Some BOOL options */ if (results[arg_norepeats].number != 0) play_repeats = FALSE; if (results[arg_nowidechars].number != 0) stave_use_widechars = FALSE; if (results[arg_drawbarlines].number != 0) bar_use_draw = TRUE; /* Draw stave lines instead of using font characters: the thickness can optionally be altered. */ if (results[arg_drawstavelines].presence != arg_present_not) stave_use_draw = results[arg_drawstavelines].number; /* Deal with stave selection */ if (results[arg_s].text != NULL) { uschar *endptr; init_strtomap(US results[arg_s].text, &endptr, main_staves); mac_setstave(main_staves, 0); if (*endptr != 0) error_moan(ERR74); /* Hard error */ } /* Deal with page selection */ if (results[arg_p].text != NULL) { uschar *s = US results[arg_p].text; stave_list *p; stave_list **pp = &output_pagelist; while (*s) { int first, last, n; int count = sscanf(CS s, "%d%n-%d%n", &first, &n, &last, &n); if (count == 0) error_moan(ERR75); else /* Hard error */ { p = malloc(sizeof(stave_list)); p->first = first; if (count == 1) p->last = first; else { if (first > last) error_moan(ERR76); /* Hard error */ else p->last = last; } p->next = NULL; *pp = p; pp = &(p->next); } s += n; if (*s == ',') s++; } } /* Deal with transposition */ if (results[arg_t].presence != arg_present_not) { main_transpose = results[arg_t].number; if (abs(main_transpose) > max_transpose) error_moan(ERR139, "T", main_transpose, max_transpose); /* Hard error */ } /* Deal with format */ if (results[arg_f].text != NULL) { int i; uschar *f = US results[arg_f].text; main_format = malloc(sizeof(f) + 1); for (i = 0; i <= Ustrlen(f); i++) main_format[i] = tolower(f[i]); } /* Deal with copies */ if (results[arg_c].presence != arg_present_not) output_copies = results[arg_c].number; /* Deal with a number of printing configuration options */ if (results[arg_reverse].number != 0) print_reverse = TRUE; if (results[arg_a4sideways].number != 0) print_pagefeed = pc_a4sideways; if (results[arg_a4ona3].number != 0) print_imposition = pc_a4ona3; if (results[arg_a5ona4].number != 0) print_imposition = pc_a5ona4; if (results[arg_incPMWfont].number != 0) output_incPMWfont = TRUE; if (results[arg_manualfeed].number != 0) output_manualfeed = TRUE; if (results[arg_duplex].number != 0) output_duplex = TRUE; if (results[arg_tumble].number != 0) output_tumble = TRUE; if (results[arg_pamphlet].number != 0) print_pamphlet = TRUE; if (results[arg_eps].number != 0) print_imposition = pc_EPS; if (results[arg_printadjustx].text != NULL) { float d; sscanf(results[arg_printadjustx].text, "%g", &d); print_image_xadjust = (int)(1000.0 * d); } if (results[arg_printadjusty].text != NULL) { float d; sscanf(results[arg_printadjusty].text, "%g", &d); print_image_yadjust = (int)(1000.0 * d); } if (results[arg_printgutter].text != NULL) { float d; sscanf(results[arg_printgutter].text, "%g", &d); print_gutter = (int)(1000.0 * d); } if (results[arg_printscale].text != NULL) { float d; sscanf(results[arg_printscale].text, "%g", &d); print_magnification = (int)(1000.0 * d); if (print_magnification == 0) error_moan(ERR11); /* Hard */ } if (results[arg_printside].presence != arg_present_not) { int n = results[arg_printside].number; if (n == 1) print_side2 = FALSE; else if (n == 2) print_side1 = FALSE; else error_moan(ERR66); /* Hard */ } } /************************************************* * Exit function * *************************************************/ /* Used to free various chunks of memory. */ static void clean_up(void) { #ifdef SUPPORT_B2PF int i; for (i = 0; i < font_tablen; i++) { if (font_b2pf_contexts[i] != NULL) b2pf_context_free(font_b2pf_contexts[i]); } #endif free(newargv); } /************************************************* * Entry Point * *************************************************/ int main(int argc, char **argv) { int i, newargc; uschar to_buffer[256]; verify = FALSE; ps_file = stdout; font_data_default = US FONTMETRICS; font_data_extra = NULL; font_music_default = US FONTDIR; font_music_extra = NULL; ps_header = US PSHEADER; midi_voices = US MIDIVOICES; midi_perc = US MIDIPERC; (void)atexit(clean_up); version_init(); font_List = malloc(MAX_FONTS * sizeof(fontstr)); font_table = malloc(font_tablen * sizeof(int)); #ifdef SUPPORT_B2PF font_b2pf_contexts = malloc(font_tablen * sizeof(b2pf_context *)); font_b2pf_options = malloc(font_tablen * sizeof(uint32_t)); for (i = 0; i < font_tablen; i++) { font_b2pf_contexts[i] = NULL; font_b2pf_options[i] = 0; } #endif beam_stemadjusts = malloc(MAX_BEAMSIZE); movement = malloc((main_max_movements+1) * sizeof(movtstr *)); /* Decode the (possibly modified) command line. */ newargv = malloc(100 * sizeof(char *)); newargc = init_command(argc, argv, newargv, arg_pattern); decode_command(newargc, newargv); if (verify) { fprintf(stderr, "PMW version %s\n", version_string); main_shownlogo = TRUE; } /* Set up default fonts */ font_init(); /* Initialize MIDI data */ read_midi_translation(&midi_voicenames, midi_voices); read_midi_translation(&midi_percnames, midi_perc); /* Initialization done */ main_initialized = TRUE; /* Sort out the input file, and possibly the name of the output file. If no input file is supplied, read stdin. Output is to stdout (default) unless overridden by -o (arg_to_name will be set). */ if (arg_from_name == NULL) { input_file = stdin; } else { main_filename = arg_from_name; input_file = Ufopen(main_filename, "r"); if (input_file == NULL) error_moan(ERR4, main_filename, strerror(errno)); /* Hard error */ if (arg_to_name == NULL) { uschar *p; arg_to_name = to_buffer; Ustrcpy(arg_to_name, arg_from_name); p = arg_to_name + Ustrlen(arg_to_name); while (p > arg_to_name && p[-1] != '.' && p[-1] != '/') p--; if (p > arg_to_name && p[-1] == '.') p[-1] = 0; Ustrcat(arg_to_name, ".ps"); } } /* Read the file */ if (verify) fprintf(stderr, "Reading ...\n"); read_start(); read_go(); if (main_rc > rc_warning) return main_rc; /* Output debugging if requested */ if (dsb_movement > 0) debug_showbar(dsb_movement, dsb_stave, dsb_bar); /* Do the typesetting */ if (verify) fprintf(stderr, "Paginating ...\n"); paginate_go(); if (main_rc > rc_warning) return main_rc; /* Show pagination information if verifying */ if (verify) display_info(); /* If a file name other than "-" is set for the output, open it. Otherwise we'll be writing to stdout. */ if (arg_to_name != NULL && Ustrcmp(arg_to_name, "-") != 0) { if (verify) fprintf(stderr, "\nWriting PostScript file \"%s\" ...\n", arg_to_name); ps_file = Ufopen(arg_to_name, "w"); if (ps_file == NULL) error_moan(ERR4, arg_to_name, strerror(errno)); /* Hard error */ } else if (verify) fprintf(stderr, "\nWriting Postscript to stdout ...\n"); /* Set up for printing, and go for it */ print_lastpage = main_lastpage; if (print_pamphlet) print_lastpage = (print_lastpage + 3) & (-4); /* This diagram shows the computed values and the positions where the origin can go in each case. In practice we take the upper value where there are two possibilities. ------------ Sideways ------------- | ------------ Upright ----------- ----- 1-up ----- ----- 2-up ----- | ---- 1-up ---- ---- 2-up ---- Port Land Port Land | Port Land Port Land x------ ------- ------- ---x--- | ----- x---- x---- ----- | 0 | | 4 | | 2 | | 6 | | | | | | | | | | ------x x------ x------ ---x--- | | 1 | | 5 | | 3 | x 7 | | | | | | | | | | | x---- ----- ----x ----- */ print_pageorigin = ((print_pagefeed == pc_a4sideways)? 0 : 1) + ((print_imposition == -1)? 0 : 2) + (opt_landscape? 4 : 0); ps_go(); if (ps_file != stdout) fclose(ps_file); /* Output warning if coupled staves were not spaced by a multiple of 4 */ if (error_111) error_moan(ERR111); /* Write MIDI output if required */ if (midi_filename != NULL) { if (verify) fprintf(stderr, "Writing MIDI file \"%s\" ...\n", midi_filename); midi_write(); } if (verify) fprintf(stderr, "PMW done\n"); return main_rc; } /* End of main.c */