chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mup / roll.c
1
2 /* Copyright (c) 1995, 1996, 1997, 1998, 2000, 2002, 2004 by Arkkra Enterprises */
3 /* All rights reserved */
4
5 /* functions to deal with rolls. This includes both parse phase and print
6  * phase code for rolls. */
7
8 #include "defines.h"
9 #include "structs.h"
10 #include "globals.h"
11
12
13
14 /* each "roll" input statement can have multiple beat offsets to specify
15  * more than one roll. This is a linked list struct to hold these offsets */
16 struct ROLLOFFSET {
17         float                   offset; /* beat offset where roll is */
18         struct ROLLOFFSET       *next;  /* linked list */
19 };
20
21
22 /* struct to hold all needed info about a roll input statement */
23 struct ROLLINFO {
24         short   topstaff;       /* roll goes from here */
25         short   topvoice;
26         short   botstaff;       /* roll ends here */
27         short   botvoice;
28         short   rolldir;        /* UP, DOWN, or UNKNOWN. UNKNOWN really means
29                                  * UP but with no arrow drawn (the default) */
30         short   error;          /* YES if got bad value for some parameter */
31         int     lineno;         /* input line number where defined */
32         char    *inputfile;     /* input file where defined */
33         struct ROLLOFFSET *offsets_p;   /* list of beat offsets */
34         struct ROLLINFO *next;  /* linked list */
35 };
36
37 static struct ROLLINFO  *Roll_list_p;   /* rolls defined in current measure */
38                 /* rolls are linked to the head of this list, so this pointer
39                  * will point to the roll currently being defined */
40
41
42 /* static functions declarations */
43 static void do_a_roll P((struct ROLLINFO *roll_p, struct MAINLL *mll_p));
44 static void roll P((struct MAINLL * mll_p, struct ROLLINFO *roll_p, 
45                 struct ROLLOFFSET * offset_p));
46 static void set_roll P((struct GRPSYL *gs_p, int item,
47                 struct ROLLINFO *roll_p));
48 static void draw_roll P((double x, double y1, double y2, int rolldir));
49
50 \f
51
52 /* allocate struct for info about roll, and add to list */
53
54 void
55 newROLLINFO()
56
57 {
58         struct ROLLINFO *roll_p;        /* newly allocated roll info */
59
60
61         MALLOC(ROLLINFO, roll_p, 1);
62
63         roll_p->offsets_p = (struct ROLLOFFSET *) 0;
64         /* assume direction will be up, but with no direction arrow */
65         roll_p->rolldir = UNKNOWN;
66         /* incomplete info so far, so mark as in error */
67         roll_p->error = YES;
68
69         /* link onto head of list */
70         roll_p->next = Roll_list_p;
71         Roll_list_p = roll_p;
72
73         roll_p->lineno = yylineno;
74         roll_p->inputfile = Curr_filename;
75 }
76 \f
77
78 /* alter the roll direction (default is UNKNOWN) */
79
80 void
81 setrolldir(int dir)
82
83 {
84         Roll_list_p->rolldir = dir;
85 }
86 \f
87
88 /* set roll parameters. Any parameter that is -1 should be left as is.
89  * Others should be error checked, and if okay, filled in */
90 /* Must be called once to fill in the top staff and voice. At that point,
91  * the bottom staff and voice are assummed to be the same as the top.
92  * If they aren't, this function should be called again
93  * with the bottom parameters set
94  * to a non-negative number and the top parameters set to -1 */
95
96 void
97 rollparam(topstaff, topvoice, botstaff, botvoice)
98
99 int topstaff;           /* top of roll is here */
100 int topvoice;
101 int botstaff;           /* bottom of roll is here */
102 int botvoice;
103
104 {
105         /* we now have enough info to check, so assume it's okay, then check */
106         Roll_list_p->error = NO;
107
108         /* for each value, if being set, error check and save away if okay.
109          * If no good, set error flag */
110         if (topstaff >= 0) {
111                 if (rangecheck(topstaff, 1, Score.staffs, "staff number")
112                                                                 == NO) {
113                         Roll_list_p->error = YES;
114                 }
115                 Roll_list_p->topstaff = Roll_list_p->botstaff
116                                                         = (short) topstaff;
117         }
118
119         if (topvoice >= 0) {
120                 if (rangecheck(topvoice, MINVOICES, NORMVOICES, "roll voice number")
121                                                         == NO) {
122                         Roll_list_p->error = YES;
123                 }
124                 Roll_list_p->topvoice = Roll_list_p->botvoice
125                                                         = (short) topvoice;
126         }
127
128         if (botstaff >= 0) {
129                 if (rangecheck(botstaff, 1, Score.staffs, "ending staff number")
130                                                         == NO) {
131                         Roll_list_p->error = YES;
132                 }
133                 Roll_list_p->botstaff = (short) botstaff;
134         }
135
136         if (botvoice >= 0) {
137                 if (rangecheck(botvoice, MINVOICES, NORMVOICES,
138                                         "ending voice number") == NO) {
139                         Roll_list_p->error = YES;
140                 }
141                 Roll_list_p->botvoice = (short) botvoice;
142         }
143
144         if (Roll_list_p->topstaff > Roll_list_p->botstaff ||
145                         (Roll_list_p->topstaff == Roll_list_p->botstaff
146                         && Roll_list_p->topvoice > Roll_list_p->botvoice)) {
147                 yyerror("end of roll must be below beginning of roll");
148                 Roll_list_p->error = YES;
149         }
150 }
151 \f
152
153 /* allocate space for offset information, fill it in, and link onto current
154  * roll information struct */
155
156 void
157 rolloffset(offset)
158
159 double offset;          /* count offset where roll is to go */
160
161 {
162         struct ROLLOFFSET *offset_p;    /* where to save offset info */
163
164
165         /* error check */
166         if (offset > Score.timenum + 1) {
167                 yyerror("roll offset beyond end of measure");
168                 Roll_list_p->error = YES;
169                 return;
170         }
171
172         /* allocate */
173         MALLOC(ROLLOFFSET, offset_p, 1);
174
175         /* fill in */
176         offset_p->offset = offset;
177
178         /* link to list */
179         offset_p->next = Roll_list_p->offsets_p;
180         Roll_list_p->offsets_p = offset_p;
181 }
182 \f
183
184 /* at end of bar, do each roll. For each roll, find closest group of
185  * top and bottom voice of roll. If they aren't at precisely the same
186  * fulltime into the measure, error. Otherwise, mark these groups STARTITEM and
187  * ENDITEM for roll, unless they are the same, in which case LONEITEM.
188  * Also find any intervening groups that are also at the same fulltime.
189  * Mark them INITEM. Finally, free the roll information. */
190
191 void
192 do_rolls(mll_p)
193
194 struct MAINLL *mll_p;   /* MAINLL of BAR */
195
196 {
197         debug(4, "do_rolls lineno=%d", mll_p->inputlineno);
198
199         do_a_roll(Roll_list_p, mll_p);
200         Roll_list_p = (struct ROLLINFO *) 0;
201 }
202 \f
203
204 /* recursively go down list of rolls, and mark the relevant GRPSYLs */
205
206 static void
207 do_a_roll(roll_p, mll_p)
208
209 struct ROLLINFO *roll_p;        /* current roll */
210 struct MAINLL *mll_p;           /* look from here to find appropriate staff */
211
212 {
213         if (roll_p == (struct ROLLINFO *) 0) {
214                 /* end recursion */
215                 return;
216         }
217
218         /* recurse */
219         do_a_roll(roll_p->next, mll_p);
220
221         /* if error in early checking, ignore this one */
222         if (roll_p->error == NO) {
223         
224                 /* find the relevant STAFF */
225                 for (   ; mll_p != (struct MAINLL *) 0; mll_p = mll_p->prev) {
226                         if (mll_p->str == S_STAFF) {
227                                 if (mll_p->u.staff_p->staffno ==
228                                                         roll_p->topstaff) {
229                                         break;
230                                 }
231                         }
232                 }
233
234                 if (mll_p == (struct MAINLL *) 0) {
235                         pfatal("couldn't find staff information for roll");
236                 }
237
238                 /* mark the GRPSYLs */
239                 roll(mll_p, roll_p, roll_p->offsets_p);
240         }
241
242         /* this one has been handled */
243         FREE(roll_p);
244 }
245 \f
246
247 /* for a specific roll on a specific chord, fill in the roll parameters for
248  * all affected, visible groups */
249
250 static void
251 roll(mll_p, roll_p, offset_p)
252
253 struct MAINLL *mll_p;           /* STAFF of top of roll */
254 struct ROLLINFO *roll_p;        /* info about the roll */
255 struct ROLLOFFSET *offset_p;    /* count offset at which to place roll */
256
257 {
258         RATIONAL timeoffset;    /* time into measure of group getting rolled */
259         RATIONAL time1offset;   /* timeoffset of group in top voice of roll */
260         struct GRPSYL *gs_p;
261         struct GRPSYL *roll_grp_p;      /* group having roll */
262         struct GRPSYL *lastvisgrp_p;    /* most recent grp with roll that is
263                                          * on a visible staff */
264         int rollstate;                  /* STARTITEM, etc */
265         int staffno;
266         int voice;
267         double top_staffscale;  /* staffscale of staff at top of roll */
268
269
270         /* recurse */
271         if (offset_p == (struct ROLLOFFSET *) 0) {
272                 return;
273         }
274         roll(mll_p, roll_p, offset_p->next);
275
276
277         /* find relevant group */
278         gs_p = mll_p->u.staff_p->groups_p [ roll_p->topvoice - 1 ];
279         roll_grp_p = closestgroup(offset_p->offset, gs_p, Score.timeden);
280
281         if (roll_grp_p->roll != NOITEM) {
282                 l_yyerror(roll_p->inputfile, roll_p->lineno,
283                                         "overlapping rolls not allowed");
284                 FREE(offset_p);
285                 return;
286         }
287
288         /* a lot of the time, the top and bottom staff/voice of roll will
289          * be the same, meaning we have a LONEITEM. This is the easy case
290          * so handle that. */
291         if (roll_p->topstaff == roll_p->botstaff
292                                 && roll_p->topvoice == roll_p->botvoice) {
293                 if (svpath(roll_p->topstaff, VISIBLE)->visible == YES) {
294                         set_roll(roll_grp_p, LONEITEM, roll_p);
295                 }
296                 FREE(offset_p);
297                 return;
298         }
299
300         /* we must have a roll that encompasses more than one voice */
301         lastvisgrp_p = (struct GRPSYL *) 0;
302
303         /* find group's actual RATIONAL time offset into the measure */
304         for (time1offset = Zero; gs_p != roll_grp_p; gs_p = gs_p->next) {
305                 time1offset = radd(time1offset, gs_p->fulltime);
306         }
307
308         /* if the top voice is visible, mark it as the top of the roll.
309          * If not, make a note that we don't have a real top yet */
310         if (svpath(roll_p->topstaff, VISIBLE)->visible == YES) {
311                 if (is_tab_staff(roll_grp_p->staffno) == YES) {
312                         l_yyerror(roll_p->inputfile, roll_p->lineno,
313                                 "roll spanning multiple voices cannot include tab staff");
314                         return;
315                 }
316                 set_roll(roll_grp_p, STARTITEM, roll_p);
317                 rollstate = INITEM;
318                 lastvisgrp_p = roll_grp_p;
319         }
320         else {
321                 rollstate = STARTITEM;
322         }
323
324         staffno = roll_p->topstaff;
325         voice = roll_p->topvoice;
326         top_staffscale = svpath(staffno, STAFFSCALE)->staffscale;
327
328         /* find all groups down to ending of roll */
329         for ( ; ; ) {
330
331                 /* move to next voice, which may be on next staff */
332                 if (++voice > MAXVOICES) {
333                         ++staffno;
334                         voice = 1;
335                         mll_p = mll_p->next;
336                         if (mll_p->str != S_STAFF ||
337                                         mll_p->u.staff_p->staffno != staffno) {
338                                 pfatal("main list messed up while doing rolls");
339                         }
340                 }
341
342                 /* if no more voices on staff, no reason to check more */
343                 if (voice > vscheme_voices(svpath(staffno, VSCHEME)->vscheme))  {
344                         continue;
345                 }
346
347                 /* find relevant group */
348                 gs_p = mll_p->u.staff_p->groups_p[ voice - 1];
349                 roll_grp_p = closestgroup(offset_p->offset, gs_p, Score.timeden);
350
351                 if (is_tab_staff(roll_grp_p->staffno) == YES) {
352                         l_yyerror(roll_p->inputfile, roll_p->lineno,
353                                 "roll spanning multiple voices cannot include tab staff");
354                         return;
355                 }
356
357                 if (svpath(roll_grp_p->staffno, STAFFSCALE)->staffscale !=
358                                                         top_staffscale) {
359                         l_yyerror(roll_p->inputfile, roll_p->lineno,
360                                 "roll cannot span staffs with differing staffscale values");
361                         return;
362                 }
363
364                 if (roll_grp_p == (struct GRPSYL *) 0) {
365                         l_yyerror(roll_p->inputfile, roll_p->lineno,
366                                         "no chord associated with roll");
367                         return;
368                 }
369
370                 /* find group's actual RATIONAL time offset into the measure */
371                 for (timeoffset = Zero; gs_p != roll_grp_p; gs_p = gs_p->next) {
372                         timeoffset = radd(timeoffset, gs_p->fulltime);
373                 }
374
375                 /* if this group's RATIONAL time isn't the same as
376                  * the top group's, it doesn't get included in the roll */
377                 if (EQ(timeoffset, time1offset)) {
378                         /* need roll on this group */
379                         if (roll_grp_p->grpcont == GC_NOTES) {
380                                 set_roll(roll_grp_p, rollstate, roll_p);
381                                 rollstate = INITEM;
382                                 if (svpath(staffno, VISIBLE)->visible == YES) {
383                                         lastvisgrp_p = roll_grp_p;
384                                 }
385                         }
386                 }
387
388                 /* check if at bottom of roll */
389                 if (staffno == roll_p->botstaff && voice == roll_p->botvoice) {
390                         if (NE(timeoffset, time1offset)) {
391                                 l_yyerror(roll_p->inputfile, roll_p->lineno,
392                                         "groups on top and bottom of roll are in different chords");
393                         }
394                         else if (svpath(staffno, VISIBLE)->visible == NO) {
395                                 /* bottom staff of roll is invisible */
396                                 if (lastvisgrp_p == (struct GRPSYL *) 0) {
397                                         /* no visible staffs in roll */
398                                         break;
399                                 }
400
401                                 /* change last visible staff included in the
402                                  * roll, to be the end of the roll, or
403                                  * set to LONEITEM if only visible */
404                                 if (lastvisgrp_p->roll == STARTITEM) {
405                                         lastvisgrp_p->roll = LONEITEM;
406                                 }
407                                 else {
408                                         lastvisgrp_p->roll = ENDITEM;
409                                 }
410                         }
411
412                         else if (lastvisgrp_p->roll == STARTITEM) {
413                                 /* all but the last were invisible */
414                                 roll_grp_p->roll = LONEITEM;
415                         }
416                         else {
417                                 roll_grp_p->roll = ENDITEM;
418                         }
419                         break;
420                 }
421         }
422
423         /* this one has been handled */
424         FREE(offset_p);
425 }
426 \f
427
428 /* do final checking and actually fill in roll parameters in grpsyl */
429
430 static void
431 set_roll(gs_p, item, roll_p)
432
433 struct GRPSYL *gs_p;            /* which GRPSYL to mark */
434 int item;                       /* LONEITEM, STARTITEM, etc */
435 struct ROLLINFO *roll_p;        /* info about roll associated with group */
436
437 {
438         if (gs_p->grpcont != GC_NOTES) {
439                 switch (item) {
440                 case STARTITEM:
441                         l_yyerror(roll_p->inputfile, roll_p->lineno,
442                                 "top visible chord of roll must not be rest or space");
443                         return;
444                 case ENDITEM:
445                         l_yyerror(roll_p->inputfile, roll_p->lineno,
446                                 "bottom visible chord of roll must not be rest or space");
447                         return;
448                 case LONEITEM:
449                         l_yyerror(roll_p->inputfile, roll_p->lineno,
450                                 "rolled chord must not be rest or space");
451                         return;
452                 }
453         }
454
455         /* fill in values */
456         gs_p->roll = (short) item;
457         gs_p->rolldir = roll_p->rolldir;
458 }
459 \f
460
461 /* print a roll */
462
463 void
464 print_roll(gs_p)
465
466 struct GRPSYL *gs_p;    /* GRPSYL that might have a roll on it */
467
468 {
469         struct GRPSYL *botgs_p;         /* bottom group of roll */
470         struct GRPSYL *prevgs_p;        /* chord above current chord */
471         float north;
472         float south;
473         float westmost;                 /* if voices overlap, the west of
474                                          * different groups in the chord may
475                                          * be different, so have to find
476                                          * whichever is farther west */
477
478         switch (gs_p->roll) {
479
480         case  LONEITEM:
481                 draw_roll(gs_p->c[AW] + ROLLPADDING * Staffscale / 2.0,
482                                 gs_p->notelist[0].c[AN],
483                                 gs_p->notelist[ gs_p->nnotes - 1].c[AS],
484                                 gs_p->rolldir);
485                 return;
486
487         case STARTITEM:
488                 /* normally, the north of the roll is the north of the top
489                  * group. However, there is one special case. If the roll
490                  * starts on voice 1 and goes through voice 2 on that staff,
491                  * and the top note on voice 2 is higher than the top note
492                  * on voice 1, need to start roll at the top note of voice 2 */
493                 north = gs_p->notelist[0].c[AN];
494                 if (gs_p->vno == 1 && gs_p->gs_p != (struct GRPSYL *) 0 &&
495                                         gs_p->gs_p->staffno == gs_p->staffno &&
496                                         gs_p->gs_p->grpsyl == GS_GROUP &&
497                                         gs_p->gs_p->grpcont == GC_NOTES) {
498                         if (gs_p->gs_p->notelist[0].c[AN] >
499                                                 gs_p->notelist[0].c[AN]) {
500                                 north = gs_p->gs_p->notelist[0].c[AN];
501                         }
502                 }
503                 
504                 westmost = gs_p->c[AW];
505
506                 /* find the bottom group of the roll */
507                 prevgs_p = gs_p;
508                 for (botgs_p = gs_p->gs_p; botgs_p != (struct GRPSYL *) 0;
509                                                 botgs_p = botgs_p->gs_p) {
510
511                         if (botgs_p->grpsyl != GS_SYLLABLE &&
512                                                 botgs_p->c[AW] < westmost) {
513                                 westmost = botgs_p->c[AW];
514                         }
515
516                         if (botgs_p->roll == ENDITEM) {
517
518                                 /* normally, the end of the roll is the bottom
519                                  * note of the ending group. However, there is
520                                  * one special case. If the roll ends on voice
521                                  * 2, and the bottom note of voice 1 is lower
522                                  * than the bottom note of voice 1, the south
523                                  * of the roll is the bottom of voice 1 */
524                                 south = botgs_p->notelist
525                                                 [ botgs_p->nnotes - 1].c[AS];
526
527                                 if (botgs_p->vno == 2 && prevgs_p->staffno
528                                                 == botgs_p->staffno &&
529                                                 prevgs_p->grpsyl == GS_GROUP) {
530
531                                         if (prevgs_p->nnotes > 0 &&
532                                                         prevgs_p->notelist[prevgs_p->nnotes-1].c[AS]
533                                                         < botgs_p->notelist
534                                                         [botgs_p->nnotes - 1]
535                                                         .c[AS]) {
536
537                                                 south = prevgs_p->notelist
538                                                         [prevgs_p->nnotes - 1]
539                                                         .c[AS];
540                                         }
541                                 }
542
543                                 draw_roll(westmost +
544                                         ROLLPADDING * Staffscale / 2.0,
545                                         north, south, gs_p->rolldir);
546                                 return;
547                         }
548                         prevgs_p = botgs_p;
549                 }
550                 pfatal("failed to find end of multi-voice roll");
551                 break;
552
553         default:
554                 /* nothing to do */
555                 break;
556         }
557 }
558 \f
559
560 /* Actually draw a roll at the given x from y1 to y2. If rolldir is DOWN,
561  * draw an arrow at the bottom; if it is UP draw an arrow at the top;
562  * if UNKNOWN don't draw any arrow. */
563
564 static void
565 draw_roll(x, y1, y2, rolldir)
566
567 double x;               /* horizontal location of roll */
568 double y1;              /* vertical location */
569 double y2;
570 int rolldir;            /* UP, DOWN, or UNKNOWN (i.e., UP but no arrow) */
571
572 {
573         /* draw the roll itself */
574         draw_wavy(x, y1, x, y2);
575
576         /* If arrow was requested, draw it */
577         if (rolldir != UNKNOWN) {
578                 float len;
579
580                 /* draw arrow at bottom */
581                 len = ROLLPADDING * Staffscale / 2.0 - Stdpad;
582                 do_linetype(L_NORMAL);
583                 if (rolldir == DOWN) {
584                         draw_line(x, y2 - Stepsize, x - (0.8 * len), y2 + len);
585                         draw_line(x, y2 - Stepsize, x + (0.8 * len), y2 + len);
586                 }
587                 else {
588                         draw_line(x, y1 + Stepsize, x - (0.8 * len), y1 - len);
589                         draw_line(x, y1 + Stepsize, x + (0.8 * len), y1 - len);
590                 }
591         }
592 }
593 \f
594
595 /* return YES if given group is the top group that gets a roll drawn by it,
596  * NO if not. This is called from the print phase */
597
598 int
599 gets_roll(gs_p, staff_p, v)
600
601 struct GRPSYL *gs_p;            /* check if this group gets a roll */
602 struct STAFF *staff_p;  /* it's connected to this staff */
603 int v;                  /* and is in this voice */
604
605 {
606         float width1, width2;           /* widest note heads in each group */
607         float maxwide;                  /* widest notehead */
608         struct GRPSYL *othergs_p;       /* group in other voice */
609
610
611         if (gs_p->roll != STARTITEM && gs_p->roll != LONEITEM) {
612                 return(NO);
613         }
614
615         /* check for strange case where we don't print a roll because groups
616          * are incompatible (had to be moved horizontally because they
617          * overlapped), and both have rolls. If the group's RX is greater
618          * than (maxwide - W_NORMAL * POINT) / 2 where
619          * maxwide is the maximum of width of the note head characters
620          * the two groups, then don't print the roll. */
621         if (svpath(staff_p->staffno, VSCHEME)->vscheme == V_1) {
622                 /* strange case only happens with 2 voices */
623                 return(YES);
624         }
625         else {
626                 /* find width of widest note of this group */
627                 width1 = widest_head(gs_p) * Staffscale;
628
629                 /* find other group. If this is first voice,
630                  * just look down the chord link */
631                 if (v == 0) {
632                         othergs_p = gs_p->gs_p;
633                 }
634                 else {
635                         /* follow groups until we find the one linked to this
636                          * one */
637                         for (othergs_p = staff_p->groups_p[0];
638                                         othergs_p != (struct GRPSYL *) 0;
639                                         othergs_p = othergs_p->next) {
640                                 if (othergs_p->gs_p == gs_p) {
641                                         break;
642                                 }
643                         }
644                 }
645
646                 if (othergs_p != (struct GRPSYL *) 0 &&
647                                 othergs_p->grpcont == GC_NOTES) {
648
649                         /* find width of widest note of the other group */
650                         width2 = widest_head(othergs_p) * Staffscale;
651
652                         maxwide = MAX(width1, width2);
653
654                         if (gs_p->c[RX] > ((maxwide - W_NORMAL * POINT) / 2.0)){
655                                 /* we hit the strange case */
656                                 return(NO);
657                         }
658                 }
659         }
660         return(YES);
661 }