2 /* Copyright (c) 1995, 1996, 1997, 1998, 2000, 2002, 2004 by Arkkra Enterprises */
3 /* All rights reserved */
5 /* functions to deal with rolls. This includes both parse phase and print
6 * phase code for rolls. */
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 */
17 float offset
; /* beat offset where roll is */
18 struct ROLLOFFSET
*next
; /* linked list */
22 /* struct to hold all needed info about a roll input statement */
24 short topstaff
; /* roll goes from here */
26 short botstaff
; /* roll ends here */
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 */
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 */
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
));
52 /* allocate struct for info about roll, and add to list */
58 struct ROLLINFO
*roll_p
; /* newly allocated roll info */
61 MALLOC(ROLLINFO
, roll_p
, 1);
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 */
69 /* link onto head of list */
70 roll_p
->next
= Roll_list_p
;
73 roll_p
->lineno
= yylineno
;
74 roll_p
->inputfile
= Curr_filename
;
78 /* alter the roll direction (default is UNKNOWN) */
84 Roll_list_p
->rolldir
= dir
;
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 */
97 rollparam(topstaff
, topvoice
, botstaff
, botvoice
)
99 int topstaff
; /* top of roll is here */
101 int botstaff
; /* bottom of roll is here */
105 /* we now have enough info to check, so assume it's okay, then check */
106 Roll_list_p
->error
= NO
;
108 /* for each value, if being set, error check and save away if okay.
109 * If no good, set error flag */
111 if (rangecheck(topstaff
, 1, Score
.staffs
, "staff number")
113 Roll_list_p
->error
= YES
;
115 Roll_list_p
->topstaff
= Roll_list_p
->botstaff
120 if (rangecheck(topvoice
, MINVOICES
, NORMVOICES
, "roll voice number")
122 Roll_list_p
->error
= YES
;
124 Roll_list_p
->topvoice
= Roll_list_p
->botvoice
129 if (rangecheck(botstaff
, 1, Score
.staffs
, "ending staff number")
131 Roll_list_p
->error
= YES
;
133 Roll_list_p
->botstaff
= (short) botstaff
;
137 if (rangecheck(botvoice
, MINVOICES
, NORMVOICES
,
138 "ending voice number") == NO
) {
139 Roll_list_p
->error
= YES
;
141 Roll_list_p
->botvoice
= (short) botvoice
;
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
;
153 /* allocate space for offset information, fill it in, and link onto current
154 * roll information struct */
159 double offset
; /* count offset where roll is to go */
162 struct ROLLOFFSET
*offset_p
; /* where to save offset info */
166 if (offset
> Score
.timenum
+ 1) {
167 yyerror("roll offset beyond end of measure");
168 Roll_list_p
->error
= YES
;
173 MALLOC(ROLLOFFSET
, offset_p
, 1);
176 offset_p
->offset
= offset
;
179 offset_p
->next
= Roll_list_p
->offsets_p
;
180 Roll_list_p
->offsets_p
= offset_p
;
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. */
194 struct MAINLL
*mll_p
; /* MAINLL of BAR */
197 debug(4, "do_rolls lineno=%d", mll_p
->inputlineno
);
199 do_a_roll(Roll_list_p
, mll_p
);
200 Roll_list_p
= (struct ROLLINFO
*) 0;
204 /* recursively go down list of rolls, and mark the relevant GRPSYLs */
207 do_a_roll(roll_p
, mll_p
)
209 struct ROLLINFO
*roll_p
; /* current roll */
210 struct MAINLL
*mll_p
; /* look from here to find appropriate staff */
213 if (roll_p
== (struct ROLLINFO
*) 0) {
219 do_a_roll(roll_p
->next
, mll_p
);
221 /* if error in early checking, ignore this one */
222 if (roll_p
->error
== NO
) {
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
==
234 if (mll_p
== (struct MAINLL
*) 0) {
235 pfatal("couldn't find staff information for roll");
238 /* mark the GRPSYLs */
239 roll(mll_p
, roll_p
, roll_p
->offsets_p
);
242 /* this one has been handled */
247 /* for a specific roll on a specific chord, fill in the roll parameters for
248 * all affected, visible groups */
251 roll(mll_p
, roll_p
, offset_p
)
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 */
258 RATIONAL timeoffset
; /* time into measure of group getting rolled */
259 RATIONAL time1offset
; /* timeoffset of group in top voice of roll */
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 */
267 double top_staffscale
; /* staffscale of staff at top of roll */
271 if (offset_p
== (struct ROLLOFFSET
*) 0) {
274 roll(mll_p
, roll_p
, offset_p
->next
);
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
);
281 if (roll_grp_p
->roll
!= NOITEM
) {
282 l_yyerror(roll_p
->inputfile
, roll_p
->lineno
,
283 "overlapping rolls not allowed");
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
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
);
300 /* we must have a roll that encompasses more than one voice */
301 lastvisgrp_p
= (struct GRPSYL
*) 0;
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
);
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");
316 set_roll(roll_grp_p
, STARTITEM
, roll_p
);
318 lastvisgrp_p
= roll_grp_p
;
321 rollstate
= STARTITEM
;
324 staffno
= roll_p
->topstaff
;
325 voice
= roll_p
->topvoice
;
326 top_staffscale
= svpath(staffno
, STAFFSCALE
)->staffscale
;
328 /* find all groups down to ending of roll */
331 /* move to next voice, which may be on next staff */
332 if (++voice
> MAXVOICES
) {
336 if (mll_p
->str
!= S_STAFF
||
337 mll_p
->u
.staff_p
->staffno
!= staffno
) {
338 pfatal("main list messed up while doing rolls");
342 /* if no more voices on staff, no reason to check more */
343 if (voice
> vscheme_voices(svpath(staffno
, VSCHEME
)->vscheme
)) {
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
);
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");
357 if (svpath(roll_grp_p
->staffno
, STAFFSCALE
)->staffscale
!=
359 l_yyerror(roll_p
->inputfile
, roll_p
->lineno
,
360 "roll cannot span staffs with differing staffscale values");
364 if (roll_grp_p
== (struct GRPSYL
*) 0) {
365 l_yyerror(roll_p
->inputfile
, roll_p
->lineno
,
366 "no chord associated with roll");
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
);
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
);
382 if (svpath(staffno
, VISIBLE
)->visible
== YES
) {
383 lastvisgrp_p
= roll_grp_p
;
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");
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 */
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
;
408 lastvisgrp_p
->roll
= ENDITEM
;
412 else if (lastvisgrp_p
->roll
== STARTITEM
) {
413 /* all but the last were invisible */
414 roll_grp_p
->roll
= LONEITEM
;
417 roll_grp_p
->roll
= ENDITEM
;
423 /* this one has been handled */
428 /* do final checking and actually fill in roll parameters in grpsyl */
431 set_roll(gs_p
, item
, roll_p
)
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 */
438 if (gs_p
->grpcont
!= GC_NOTES
) {
441 l_yyerror(roll_p
->inputfile
, roll_p
->lineno
,
442 "top visible chord of roll must not be rest or space");
445 l_yyerror(roll_p
->inputfile
, roll_p
->lineno
,
446 "bottom visible chord of roll must not be rest or space");
449 l_yyerror(roll_p
->inputfile
, roll_p
->lineno
,
450 "rolled chord must not be rest or space");
456 gs_p
->roll
= (short) item
;
457 gs_p
->rolldir
= roll_p
->rolldir
;
466 struct GRPSYL
*gs_p
; /* GRPSYL that might have a roll on it */
469 struct GRPSYL
*botgs_p
; /* bottom group of roll */
470 struct GRPSYL
*prevgs_p
; /* chord above current chord */
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 */
478 switch (gs_p
->roll
) {
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
],
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
];
504 westmost
= gs_p
->c
[AW
];
506 /* find the bottom group of the roll */
508 for (botgs_p
= gs_p
->gs_p
; botgs_p
!= (struct GRPSYL
*) 0;
509 botgs_p
= botgs_p
->gs_p
) {
511 if (botgs_p
->grpsyl
!= GS_SYLLABLE
&&
512 botgs_p
->c
[AW
] < westmost
) {
513 westmost
= botgs_p
->c
[AW
];
516 if (botgs_p
->roll
== ENDITEM
) {
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
];
527 if (botgs_p
->vno
== 2 && prevgs_p
->staffno
528 == botgs_p
->staffno
&&
529 prevgs_p
->grpsyl
== GS_GROUP
) {
531 if (prevgs_p
->nnotes
> 0 &&
532 prevgs_p
->notelist
[prevgs_p
->nnotes
-1].c
[AS
]
534 [botgs_p
->nnotes
- 1]
537 south
= prevgs_p
->notelist
538 [prevgs_p
->nnotes
- 1]
544 ROLLPADDING
* Staffscale
/ 2.0,
545 north
, south
, gs_p
->rolldir
);
550 pfatal("failed to find end of multi-voice roll");
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. */
565 draw_roll(x
, y1
, y2
, rolldir
)
567 double x
; /* horizontal location of roll */
568 double y1
; /* vertical location */
570 int rolldir
; /* UP, DOWN, or UNKNOWN (i.e., UP but no arrow) */
573 /* draw the roll itself */
574 draw_wavy(x
, y1
, x
, y2
);
576 /* If arrow was requested, draw it */
577 if (rolldir
!= UNKNOWN
) {
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
);
588 draw_line(x
, y1
+ Stepsize
, x
- (0.8 * len
), y1
- len
);
589 draw_line(x
, y1
+ Stepsize
, x
+ (0.8 * len
), y1
- len
);
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 */
599 gets_roll(gs_p
, staff_p
, v
)
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 */
606 float width1
, width2
; /* widest note heads in each group */
607 float maxwide
; /* widest notehead */
608 struct GRPSYL
*othergs_p
; /* group in other voice */
611 if (gs_p
->roll
!= STARTITEM
&& gs_p
->roll
!= LONEITEM
) {
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 */
626 /* find width of widest note of this group */
627 width1
= widest_head(gs_p
) * Staffscale
;
629 /* find other group. If this is first voice,
630 * just look down the chord link */
632 othergs_p
= gs_p
->gs_p
;
635 /* follow groups until we find the one linked to this
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
) {
646 if (othergs_p
!= (struct GRPSYL
*) 0 &&
647 othergs_p
->grpcont
== GC_NOTES
) {
649 /* find width of widest note of the other group */
650 width2
= widest_head(othergs_p
) * Staffscale
;
652 maxwide
= MAX(width1
, width2
);
654 if (gs_p
->c
[RX
] > ((maxwide
- W_NORMAL
* POINT
) / 2.0)){
655 /* we hit the strange case */