Commit | Line | Data |
---|---|---|
fac14bbe MW |
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 | } |