2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2005 Jan Nieuwenhuizen <janneke@gnu.org>
7 Han-Wen Nienhuys <hanwen@xs4all.nl>
13 - tuplet bracket should probably be subject to the same rules as
14 beam sloping/quanting.
16 - There is no support for kneed brackets, or nested brackets.
18 - number placement for parallel beams should be much more advanced:
19 for sloped beams some extra horizontal offset must be introduced.
21 - number placement is usually done over the center note, not the
26 TODO: quantise, we don't want to collide with staff lines.
27 (or should we be above staff?)
29 todo: handle breaking elegantly.
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
37 #include "output-def.hh"
38 #include "font-interface.hh"
39 #include "text-interface.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
45 #include "staff-symbol-referencer.hh"
49 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
51 Spanner *me = dynamic_cast<Spanner *> (me_grob);
52 Item *g = me->get_bound (hdir);
53 if (Note_column::has_interface (g)
54 && Note_column::get_stem (g)
55 && Note_column::dir (g) == my_dir)
56 g = Note_column::get_stem (g);
63 flatten_number_pair_property (Grob *me,
64 Direction xdir, SCM sym)
66 Drul_array<Real> zero (0, 0);
67 Drul_array<Real> pair = robust_scm2drul (me->internal_get_property (sym), zero);
70 me->internal_set_property (sym, ly_interval2scm (pair));
75 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
77 Spanner *me = dynamic_cast<Spanner *> (me_grob);
79 if (me->get_bound (LEFT)->break_status_dir ()
80 || me->get_bound (RIGHT)->break_status_dir ())
83 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
84 Note_column::get_stem (cols.top ()));
86 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
87 != me->get_bound (RIGHT)->get_column())
90 Drul_array<Grob*> beams;
93 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
94 } while (flip (&d) != LEFT);
96 *equally_long = false;
97 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
100 extract_grob_set (beams[LEFT], "stems", beam_stems);
101 if (beam_stems.size () == 0)
103 programming_error ("beam under tuplet bracket has no stems");
108 *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
113 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_connect_to_neighbors,1);
115 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
117 Spanner *me = unsmob_spanner (smob);
119 Direction dir = get_grob_direction (me);
120 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
121 get_x_bound_item (me, RIGHT, dir));
123 Drul_array<bool> connect_to_other (false, false);
127 Direction break_dir = bounds[d]->break_status_dir ();
129 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
131 int neighbor_idx = me->get_break_index () - break_dir;
134 && neighbor_idx < orig_spanner->broken_intos_.size ())
136 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
138 /* trigger possible suicide*/
139 (void) neighbor->get_property ("positions");
144 && (neighbor_idx < orig_spanner->broken_intos_.size ()
145 && neighbor_idx >= 0)
146 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
148 while (flip (&d) != LEFT);
151 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
152 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
153 scm_from_bool (connect_to_other[RIGHT]));
159 Tuplet_bracket::get_common_x (Spanner *me)
161 extract_grob_set (me, "note-columns", columns);
163 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
164 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
165 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
170 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_control_points,1)
172 Tuplet_bracket::calc_control_points (SCM smob)
174 Spanner *me = unsmob_spanner (smob);
176 extract_grob_set (me, "note-columns", columns);
178 Drul_array<Real> positions
179 = ly_scm2realdrul (me->get_property ("positions"));
181 Grob *commonx = get_common_x (me);
182 Direction dir = get_grob_direction (me);
184 Drul_array<Item *> bounds;
185 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
186 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
188 Drul_array<bool> connect_to_other =
189 robust_scm2booldrul (me->get_property ("connect-to-other"),
190 Drul_array<bool> (false, false));
197 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
199 if (connect_to_other[d])
201 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
202 Interval (-0.5, 0.0)));
205 x_span[d] += d * overshoot[d];
207 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
211 && (columns.is_empty ()
212 || (bounds[d]->get_column ()
213 != dynamic_cast<Item *> (columns.top ())->get_column ())))
216 TODO: make padding tunable?
220 if (bounds[d]->break_status_dir ())
223 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
226 while (flip (&d) != LEFT);
230 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
231 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
232 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
238 in the case that there is no bracket, but there is a (single) beam,
239 follow beam precisely for determining tuplet number location.
241 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
243 Tuplet_bracket::print (SCM smob)
245 Spanner *me = unsmob_spanner (smob);
248 extract_grob_set (me, "note-columns", columns);
249 bool equally_long = false;
250 Grob *par_beam = parallel_beam (me, columns, &equally_long);
252 bool bracket_visibility = !(par_beam && equally_long);
254 Fixme: the type of this prop is sucky.
256 SCM bracket = me->get_property ("bracket-visibility");
257 if (scm_is_bool (bracket))
258 bracket_visibility = ly_scm2bool (bracket);
259 else if (bracket == ly_symbol2scm ("if-no-beam"))
260 bracket_visibility = !par_beam;
263 SCM cpoints = me->get_property ("control-points");
264 if (scm_ilength (cpoints) < 2)
270 Drul_array<Offset> points;
271 points[LEFT] = ly_scm2offset (scm_car (cpoints));
272 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
274 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
275 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
279 Output_def *pap = me->layout ();
281 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
284 No bracket when it would be smaller than the number.
287 if (bracket_visibility && number_grob)
289 Interval ext = number_grob->extent (number_grob, X_AXIS);
290 if (!ext.is_empty ())
292 gap = ext.length () + 1.0;
294 if (0.75 * x_span.length () < gap)
295 bracket_visibility = false;
299 if (bracket_visibility)
301 Drul_array<Real> zero (0, 0);
302 Real ss = Staff_symbol_referencer::staff_space (me);
303 Drul_array<Real> height
304 = robust_scm2drul (me->get_property ("edge-height"), zero);
305 Drul_array<Real> flare
306 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
307 Drul_array<Real> shorten
308 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
309 Drul_array<Stencil> edge_stencils;
311 Direction dir = get_grob_direction (me);
313 scale_drul (&height, -ss * dir);
314 scale_drul (&flare, ss);
315 scale_drul (&shorten, ss);
317 Drul_array<bool> connect_to_other =
318 robust_scm2booldrul (me->get_property ("connect-to-other"),
319 Drul_array<bool> (false, false));
324 if (connect_to_other[d])
330 SCM edge_text = me->get_property ("edge-text");
332 if (scm_is_pair (edge_text))
334 SCM properties = Font_interface::text_font_alist_chain (me);
335 SCM text = index_get_cell (edge_text, d);
336 if (Text_interface::is_markup (text))
338 SCM t = Text_interface::interpret_markup (pap->self_scm (),
341 Stencil *edge_text = unsmob_stencil (t);
342 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
343 edge_stencils[d] = *edge_text;
348 while (flip (&d) != LEFT);
350 Stencil brack = make_bracket (me, Y_AXIS,
351 points[RIGHT] - points[LEFT],
354 0.1 = more space at right due to italics
355 TODO: use italic correction of font.
357 Interval (-0.5, 0.5) * gap + 0.1,
362 if (!edge_stencils[d].is_empty ())
363 brack.add_stencil (edge_stencils[d]);
365 while (flip (&d) != LEFT);
367 mol.add_stencil (brack);
370 mol.translate (points[LEFT]);
371 return mol.smobbed_copy ();
375 should move to lookup?
377 TODO: this will fail for very short (shorter than the flare)
381 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
384 Drul_array<Real> height,
386 Drul_array<Real> flare,
387 Drul_array<Real> shorten)
389 Drul_array<Offset> corners (Offset (0, 0), dz);
391 Real length = dz.length ();
392 Drul_array<Offset> gap_corners;
394 Axis bracket_axis = other_axis (protusion_axis);
396 Drul_array<Offset> straight_corners = corners;
400 straight_corners[d] += -d * shorten[d] / length * dz;
401 while (flip (&d) != LEFT)
405 gap = Interval (0, 0);
407 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
408 while (flip (&d) != LEFT)
411 Drul_array<Offset> flare_corners = straight_corners;
414 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
415 flare_corners[d][protusion_axis] += height[d];
416 straight_corners[d][bracket_axis] += -d * flare[d];
418 while (flip (&d) != LEFT);
423 m.add_stencil (Line_interface::line (me, straight_corners[d],
426 m.add_stencil (Line_interface::line (me, straight_corners[d],
429 while (flip (&d) != LEFT);
435 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
437 extract_grob_set (me, "note-columns", columns);
439 while (l < columns.size () && Note_column::has_rests (columns[l]))
442 int r = columns.size ()- 1;
443 while (r >= l && Note_column::has_rests (columns[r]))
456 use first -> last note for slope, and then correct for disturbing
459 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
461 Spanner *me = dynamic_cast<Spanner *> (me_grob);
463 extract_grob_set (me, "note-columns", columns);
464 extract_grob_set (me, "tuplets", tuplets);
466 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
467 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
468 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
469 commony = st->common_refpoint (commony, Y_AXIS);
471 Grob *commonx = get_common_x (me);
472 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
475 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
477 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
480 staff = st->extent (commony, Y_AXIS);
485 Direction dir = get_grob_direction (me);
488 Use outer non-rest columns to determine slope
492 get_bounds (me, &left_col, &right_col);
493 if (left_col && right_col)
495 Interval rv = right_col->extent (commony, Y_AXIS);
496 Interval lv = left_col->extent (commony, Y_AXIS);
499 Real graphical_dy = rv[dir] - lv[dir];
501 Slice ls = Note_column::head_positions_interval (left_col);
502 Slice rs = Note_column::head_positions_interval (right_col);
505 musical_dy[UP] = rs[UP] - ls[UP];
506 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
507 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
509 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
517 *offset = -dir * infinity_f;
519 Item *lgr = get_x_bound_item (me, LEFT, dir);
520 Item *rgr = get_x_bound_item (me, RIGHT, dir);
521 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
522 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
524 Array<Offset> points;
525 points.push (Offset (x0 - x0, staff[dir]));
526 points.push (Offset (x1 - x0, staff[dir]));
528 for (int i = 0; i < columns.size (); i++)
530 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
531 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
533 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
534 points.push (Offset (x, notey));
538 This is a slight hack. We compute two encompass points from the
539 bbox of the smaller tuplets.
541 We assume that the smaller bracket is 1.0 space high.
543 Real ss = Staff_symbol_referencer::staff_space (me);
544 for (int i = 0; i < tuplets.size (); i++)
546 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
547 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
550 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
553 Real other_dy = positions[RIGHT] - positions[LEFT];
558 = tuplet_y.linear_combination (d * sign (other_dy));
562 Let's not take padding into account for nested tuplets.
563 the edges can come very close to the stems, likewise for
566 Drul_array<Real> my_height
567 = robust_scm2drul (me->get_property ("edge-height"),
569 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
570 == me->get_bound (d))
571 y += dir * my_height[d];
574 points.push (Offset (tuplet_x[d] - x0, y));
576 while (flip (&d) != LEFT);
579 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
580 for (int i = 0; i < points.size (); i++)
582 Real x = points[i][X_AXIS];
583 Real tuplety = (*dy) * x * factor;
585 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
586 *offset = points[i][Y_AXIS] - tuplety;
589 *offset += scm_to_double (me->get_property ("padding")) * dir;
592 horizontal brackets should not collide with staff lines.
594 Kind of pointless since we put them outside the staff anyway, but
595 let's leave code for the future when possibly allow them to move
596 into the staff once again.
599 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
601 // quantize, then do collision check.
604 *offset = rint (*offset);
605 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
613 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
615 Tuplet_bracket::calc_direction (SCM smob)
617 Grob *me = unsmob_grob (smob);
618 Direction dir = Tuplet_bracket::get_default_dir (me);
619 return scm_from_int (dir);
622 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
624 Tuplet_bracket::calc_positions (SCM smob)
626 Grob *me = unsmob_grob (smob);
627 extract_grob_set (me, "note-columns", columns);
629 if (columns.is_empty())
632 return scm_cons (scm_from_double (0),
633 scm_from_double (0));
636 Direction dir = get_grob_direction (me);
637 bool equally_long = false;
638 Grob *par_beam = parallel_beam (me, columns, &equally_long);
641 We follow the beam only if there is one, and we are next to it.
646 || get_grob_direction (par_beam) != dir)
647 calc_position_and_height (me, &offset, &dy);
650 SCM ps = par_beam->get_property ("positions");
652 Real lp = scm_to_double (scm_car (ps));
653 Real rp = scm_to_double (scm_cdr (ps));
658 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
663 SCM x = scm_cons (scm_from_double (offset),
664 scm_from_double (offset + dy));
673 Tuplet_bracket::get_default_dir (Grob *me)
675 Drul_array<int> dirs (0, 0);
676 extract_grob_set (me, "note-columns", columns);
677 for (int i = 0; i < columns.size (); i++)
679 Grob *nc = columns[i];
680 Direction d = Note_column::dir (nc);
685 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
689 Tuplet_bracket::add_column (Grob *me, Item *n)
691 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
692 add_bound_item (dynamic_cast<Spanner *> (me), n);
696 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
698 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
701 ADD_INTERFACE (Tuplet_bracket,
702 "tuplet-bracket-interface",
703 "A bracket with a number in the middle, used for tuplets. "
704 "When the bracket spans a line break, the value of "
705 "@code{break-overshoot} determines how far it extends "
707 "At a line break, the markups in the @code{edge-text} are printed "
713 "bracket-visibility "
715 "connect-to-neighbor "