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 "font-interface.hh"
38 #include "output-def.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);
62 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
64 Spanner *me = dynamic_cast<Spanner *> (me_grob);
66 if (me->get_bound (LEFT)->break_status_dir ()
67 || me->get_bound (RIGHT)->break_status_dir ())
70 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
71 Note_column::get_stem (cols.top ()));
73 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
74 != me->get_bound (RIGHT)->get_column())
77 Drul_array<Grob*> beams;
80 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
81 } while (flip (&d) != LEFT);
83 *equally_long = false;
84 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
87 extract_grob_set (beams[LEFT], "stems", beam_stems);
88 if (beam_stems.size () == 0)
90 programming_error ("beam under tuplet bracket has no stems");
95 *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
102 in the case that there is no bracket, but there is a (single) beam,
103 follow beam precisely for determining tuplet number location.
105 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
107 Tuplet_bracket::print (SCM smob)
109 Spanner *me = unsmob_spanner (smob);
111 extract_grob_set (me, "note-columns", columns);
113 Drul_array<Real> positions = ly_scm2realdrul (me->get_property ("positions"));
114 Real dy = positions[RIGHT] - positions[LEFT];
115 bool equally_long = false;
116 Grob *par_beam = parallel_beam (me, columns, &equally_long);
117 Spanner *sp = dynamic_cast<Spanner *> (me);
119 bool bracket_visibility = !(par_beam && equally_long);
120 bool number_visibility = true;
123 Fixme: the type of this prop is sucky.
125 SCM bracket = me->get_property ("bracket-visibility");
126 if (scm_is_bool (bracket))
127 bracket_visibility = ly_scm2bool (bracket);
128 else if (bracket == ly_symbol2scm ("if-no-beam"))
129 bracket_visibility = !par_beam;
131 SCM numb = me->get_property ("number-visibility");
132 if (scm_is_bool (numb))
133 number_visibility = ly_scm2bool (numb);
134 else if (numb == ly_symbol2scm ("if-no-beam"))
135 number_visibility = !par_beam;
137 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
138 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
139 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
141 Direction dir = get_grob_direction (me);
143 Drul_array<Item *> bounds;
144 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
145 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
147 Drul_array<bool> connect_to_other;
152 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
153 Direction break_dir = bounds[d]->break_status_dir ();
154 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
156 int neighbor_idx = me->get_break_index () - break_dir;
160 && neighbor_idx < orig_spanner->broken_intos_.size ())
162 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
164 /* trigger possible suicide*/
165 (void) neighbor->get_property ("positions");
170 && (neighbor_idx < orig_spanner->broken_intos_.size ()
171 && neighbor_idx >= 0)
172 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
175 if (connect_to_other[d])
177 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
178 Interval (-0.5, 0.0)));
181 x_span[d] += d * overshoot[d];
183 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
187 && (columns.is_empty ()
188 || (bounds[d]->get_column ()
189 != dynamic_cast<Item *> (columns.top ())->get_column ())))
192 TODO: make padding tunable?
196 if (bounds[d]->break_status_dir ())
199 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
202 while (flip (&d) != LEFT);
204 Real w = x_span.length ();
205 SCM number = me->get_property ("text");
207 Output_def *pap = me->layout ();
209 if (scm_is_string (number) && number_visibility)
211 SCM properties = Font_interface::text_font_alist_chain (me);
212 SCM snum = Text_interface::interpret_markup (pap->self_scm (),
214 num = *unsmob_stencil (snum);
215 num.align_to (X_AXIS, CENTER);
216 num.translate_axis (w / 2, X_AXIS);
217 num.align_to (Y_AXIS, CENTER);
219 num.translate_axis (dy / 2, Y_AXIS);
221 mol.add_stencil (num);
225 No bracket when it would be smaller than the number.
227 TODO: should use GAP in calculation too.
229 if (bracket_visibility && number_visibility
230 && mol.extent (X_AXIS).length () > w)
231 bracket_visibility = false;
233 if (bracket_visibility)
237 if (!num.extent (X_AXIS).is_empty ())
238 gap = num.extent (X_AXIS).length () + 1.0;
240 Drul_array<Real> zero (0, 0);
241 Real ss = Staff_symbol_referencer::staff_space (me);
242 Drul_array<Real> height
243 = robust_scm2drul (me->get_property ("edge-height"), zero);
244 Drul_array<Real> flare
245 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
246 Drul_array<Real> shorten
247 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
248 Drul_array<Stencil> edge_stencils;
250 scale_drul (&height, -ss * dir);
251 scale_drul (&flare, ss);
252 scale_drul (&shorten, ss);
255 if (connect_to_other[d])
261 SCM edge_text = me->get_property ("edge-text");
263 if (scm_is_pair (edge_text))
265 SCM properties = Font_interface::text_font_alist_chain (me);
266 SCM text = index_get_cell (edge_text, d);
267 if (Text_interface::is_markup (text))
269 SCM t = Text_interface::interpret_markup (pap->self_scm (),
272 Stencil *edge_text = unsmob_stencil (t);
273 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
274 edge_stencils[d] = *edge_text;
279 while (flip (&d) != LEFT);
281 Stencil brack = make_bracket (me, Y_AXIS,
285 0.1 = more space at right due to italics
286 TODO: use italic correction of font.
288 Interval (-0.5, 0.5) * gap + 0.1,
293 if (!edge_stencils[d].is_empty ())
294 brack.add_stencil (edge_stencils[d]);
296 while (flip (&d) != LEFT);
298 mol.add_stencil (brack);
301 mol.translate_axis (positions[LEFT], Y_AXIS);
302 mol.translate_axis (x_span[LEFT]
303 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
304 return mol.smobbed_copy ();
308 should move to lookup?
310 TODO: this will fail for very short (shorter than the flare)
314 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
317 Drul_array<Real> height,
319 Drul_array<Real> flare,
320 Drul_array<Real> shorten)
322 Drul_array<Offset> corners (Offset (0, 0), dz);
324 Real length = dz.length ();
325 Drul_array<Offset> gap_corners;
327 Axis bracket_axis = other_axis (protusion_axis);
329 Drul_array<Offset> straight_corners = corners;
333 straight_corners[d] += -d * shorten[d] / length * dz;
334 while (flip (&d) != LEFT)
338 gap = Interval (0, 0);
340 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
341 while (flip (&d) != LEFT)
344 Drul_array<Offset> flare_corners = straight_corners;
347 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
348 flare_corners[d][protusion_axis] += height[d];
349 straight_corners[d][bracket_axis] += -d * flare[d];
351 while (flip (&d) != LEFT);
356 m.add_stencil (Line_interface::line (me, straight_corners[d],
359 m.add_stencil (Line_interface::line (me, straight_corners[d],
362 while (flip (&d) != LEFT);
368 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
370 extract_grob_set (me, "note-columns", columns);
372 while (l < columns.size () && Note_column::has_rests (columns[l]))
375 int r = columns.size ()- 1;
376 while (r >= l && Note_column::has_rests (columns[r]))
389 use first -> last note for slope, and then correct for disturbing
392 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
394 Spanner *me = dynamic_cast<Spanner *> (me_grob);
396 extract_grob_set (me, "note-columns", columns);
397 extract_grob_set (me, "tuplets", tuplets);
399 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
400 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
401 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
402 commony = st->common_refpoint (commony, Y_AXIS);
404 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
405 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
406 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
407 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
410 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
412 staff = st->extent (commony, Y_AXIS);
413 Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
417 Direction dir = get_grob_direction (me);
420 Use outer non-rest columns to determine slope
424 get_bounds (me, &left_col, &right_col);
425 if (left_col && right_col)
427 Interval rv = right_col->extent (commony, Y_AXIS);
428 Interval lv = left_col->extent (commony, Y_AXIS);
431 Real graphical_dy = rv[dir] - lv[dir];
433 Slice ls = Note_column::head_positions_interval (left_col);
434 Slice rs = Note_column::head_positions_interval (right_col);
437 musical_dy[UP] = rs[UP] - ls[UP];
438 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
439 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
441 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
449 *offset = -dir * infinity_f;
451 Item *lgr = get_x_bound_item (me, LEFT, dir);
452 Item *rgr = get_x_bound_item (me, RIGHT, dir);
453 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
454 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
456 Array<Offset> points;
457 points.push (Offset (x0 - x0, staff[dir]));
458 points.push (Offset (x1 - x0, staff[dir]));
460 for (int i = 0; i < columns.size (); i++)
462 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
463 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
465 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
466 points.push (Offset (x, notey));
470 This is a slight hack. We compute two encompass points from the
471 bbox of the smaller tuplets.
473 We assume that the smaller bracket is 1.0 space high.
475 Real ss = Staff_symbol_referencer::staff_space (me);
476 for (int i = 0; i < tuplets.size (); i++)
478 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
479 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
482 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
485 Real other_dy = positions[RIGHT] - positions[LEFT];
490 = tuplet_y.linear_combination (d * sign (other_dy));
494 Let's not take padding into account for nested tuplets.
495 the edges can come very close to the stems, likewise for
498 Drul_array<Real> my_height
499 = robust_scm2drul (me->get_property ("edge-height"),
501 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
502 == me->get_bound (d))
503 y += dir * my_height[d];
506 points.push (Offset (tuplet_x[d] - x0, y));
508 while (flip (&d) != LEFT);
511 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
512 for (int i = 0; i < points.size (); i++)
514 Real x = points[i][X_AXIS];
515 Real tuplety = (*dy) * x * factor;
517 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
518 *offset = points[i][Y_AXIS] - tuplety;
521 *offset += scm_to_double (me->get_property ("padding")) * dir;
524 horizontal brackets should not collide with staff lines.
526 Kind of pointless since we put them outside the staff anyway, but
527 let's leave code for the future when possibly allow them to move
528 into the staff once again.
531 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
533 // quantize, then do collision check.
536 *offset = rint (*offset);
537 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
545 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
547 Tuplet_bracket::calc_direction (SCM smob)
549 Grob *me = unsmob_grob (smob);
550 Direction dir = Tuplet_bracket::get_default_dir (me);
551 return scm_from_int (dir);
554 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
556 Tuplet_bracket::calc_positions (SCM smob)
558 Grob *me = unsmob_grob (smob);
559 extract_grob_set (me, "note-columns", columns);
561 if (columns.is_empty())
564 return scm_cons (scm_from_double (0),
565 scm_from_double (0));
568 Direction dir = get_grob_direction (me);
569 bool equally_long = false;
570 Grob *par_beam = parallel_beam (me, columns, &equally_long);
573 We follow the beam only if there is one, and we are next to it.
578 || get_grob_direction (par_beam) != dir)
579 calc_position_and_height (me, &offset, &dy);
582 SCM ps = par_beam->get_property ("positions");
584 Real lp = scm_to_double (scm_car (ps));
585 Real rp = scm_to_double (scm_cdr (ps));
590 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
595 SCM x = scm_cons (scm_from_double (offset),
596 scm_from_double (offset + dy));
605 Tuplet_bracket::get_default_dir (Grob *me)
607 Drul_array<int> dirs (0, 0);
608 extract_grob_set (me, "note-columns", columns);
609 for (int i = 0; i < columns.size (); i++)
611 Grob *nc = columns[i];
612 Direction d = Note_column::dir (nc);
617 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
621 Tuplet_bracket::add_column (Grob *me, Item *n)
623 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
624 add_bound_item (dynamic_cast<Spanner *> (me), n);
628 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
630 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
633 ADD_INTERFACE (Tuplet_bracket,
634 "tuplet-bracket-interface",
635 "A bracket with a number in the middle, used for tuplets. "
636 "When the bracket spans a line break, the value of "
637 "@code{break-overshoot} determines how far it extends "
639 "At a line break, the markups in the @code{edge-text} are printed "
645 "bracket-visibility "