chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mup / roll.c
CommitLineData
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 */
16struct 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 */
23struct 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
37static 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 */
43static void do_a_roll P((struct ROLLINFO *roll_p, struct MAINLL *mll_p));
44static void roll P((struct MAINLL * mll_p, struct ROLLINFO *roll_p,
45 struct ROLLOFFSET * offset_p));
46static void set_roll P((struct GRPSYL *gs_p, int item,
47 struct ROLLINFO *roll_p));
48static 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
54void
55newROLLINFO()
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
80void
81setrolldir(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
96void
97rollparam(topstaff, topvoice, botstaff, botvoice)
98
99int topstaff; /* top of roll is here */
100int topvoice;
101int botstaff; /* bottom of roll is here */
102int 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
156void
157rolloffset(offset)
158
159double 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
191void
192do_rolls(mll_p)
193
194struct 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
206static void
207do_a_roll(roll_p, mll_p)
208
209struct ROLLINFO *roll_p; /* current roll */
210struct 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
250static void
251roll(mll_p, roll_p, offset_p)
252
253struct MAINLL *mll_p; /* STAFF of top of roll */
254struct ROLLINFO *roll_p; /* info about the roll */
255struct 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
430static void
431set_roll(gs_p, item, roll_p)
432
433struct GRPSYL *gs_p; /* which GRPSYL to mark */
434int item; /* LONEITEM, STARTITEM, etc */
435struct 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
463void
464print_roll(gs_p)
465
466struct 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
564static void
565draw_roll(x, y1, y2, rolldir)
566
567double x; /* horizontal location of roll */
568double y1; /* vertical location */
569double y2;
570int 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
598int
599gets_roll(gs_p, staff_p, v)
600
601struct GRPSYL *gs_p; /* check if this group gets a roll */
602struct STAFF *staff_p; /* it's connected to this staff */
603int 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}