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 Drul_array<Offset> points;
265 points[LEFT] = ly_scm2offset (scm_car (cpoints));
266 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
268 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
269 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
273 Output_def *pap = me->layout ();
275 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
278 No bracket when it would be smaller than the number.
281 if (bracket_visibility && number_grob)
283 Interval ext = number_grob->extent (number_grob, X_AXIS);
284 if (!ext.is_empty ())
286 gap = ext.length () + 1.0;
288 if (0.75 * x_span.length () < gap)
289 bracket_visibility = false;
293 if (bracket_visibility)
295 Drul_array<Real> zero (0, 0);
296 Real ss = Staff_symbol_referencer::staff_space (me);
297 Drul_array<Real> height
298 = robust_scm2drul (me->get_property ("edge-height"), zero);
299 Drul_array<Real> flare
300 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
301 Drul_array<Real> shorten
302 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
303 Drul_array<Stencil> edge_stencils;
305 Direction dir = get_grob_direction (me);
307 scale_drul (&height, -ss * dir);
308 scale_drul (&flare, ss);
309 scale_drul (&shorten, ss);
311 Drul_array<bool> connect_to_other =
312 robust_scm2booldrul (me->get_property ("connect-to-other"),
313 Drul_array<bool> (false, false));
318 if (connect_to_other[d])
324 SCM edge_text = me->get_property ("edge-text");
326 if (scm_is_pair (edge_text))
328 SCM properties = Font_interface::text_font_alist_chain (me);
329 SCM text = index_get_cell (edge_text, d);
330 if (Text_interface::is_markup (text))
332 SCM t = Text_interface::interpret_markup (pap->self_scm (),
335 Stencil *edge_text = unsmob_stencil (t);
336 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
337 edge_stencils[d] = *edge_text;
342 while (flip (&d) != LEFT);
344 Stencil brack = make_bracket (me, Y_AXIS,
345 points[RIGHT] - points[LEFT],
348 0.1 = more space at right due to italics
349 TODO: use italic correction of font.
351 Interval (-0.5, 0.5) * gap + 0.1,
356 if (!edge_stencils[d].is_empty ())
357 brack.add_stencil (edge_stencils[d]);
359 while (flip (&d) != LEFT);
361 mol.add_stencil (brack);
364 mol.translate (points[LEFT]);
365 return mol.smobbed_copy ();
369 should move to lookup?
371 TODO: this will fail for very short (shorter than the flare)
375 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
378 Drul_array<Real> height,
380 Drul_array<Real> flare,
381 Drul_array<Real> shorten)
383 Drul_array<Offset> corners (Offset (0, 0), dz);
385 Real length = dz.length ();
386 Drul_array<Offset> gap_corners;
388 Axis bracket_axis = other_axis (protusion_axis);
390 Drul_array<Offset> straight_corners = corners;
394 straight_corners[d] += -d * shorten[d] / length * dz;
395 while (flip (&d) != LEFT)
399 gap = Interval (0, 0);
401 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
402 while (flip (&d) != LEFT)
405 Drul_array<Offset> flare_corners = straight_corners;
408 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
409 flare_corners[d][protusion_axis] += height[d];
410 straight_corners[d][bracket_axis] += -d * flare[d];
412 while (flip (&d) != LEFT);
417 m.add_stencil (Line_interface::line (me, straight_corners[d],
420 m.add_stencil (Line_interface::line (me, straight_corners[d],
423 while (flip (&d) != LEFT);
429 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
431 extract_grob_set (me, "note-columns", columns);
433 while (l < columns.size () && Note_column::has_rests (columns[l]))
436 int r = columns.size ()- 1;
437 while (r >= l && Note_column::has_rests (columns[r]))
450 use first -> last note for slope, and then correct for disturbing
453 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
455 Spanner *me = dynamic_cast<Spanner *> (me_grob);
457 extract_grob_set (me, "note-columns", columns);
458 extract_grob_set (me, "tuplets", tuplets);
460 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
461 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
462 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
463 commony = st->common_refpoint (commony, Y_AXIS);
465 Grob *commonx = get_common_x (me);
466 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
469 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
471 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
474 staff = st->extent (commony, Y_AXIS);
479 Direction dir = get_grob_direction (me);
482 Use outer non-rest columns to determine slope
486 get_bounds (me, &left_col, &right_col);
487 if (left_col && right_col)
489 Interval rv = right_col->extent (commony, Y_AXIS);
490 Interval lv = left_col->extent (commony, Y_AXIS);
493 Real graphical_dy = rv[dir] - lv[dir];
495 Slice ls = Note_column::head_positions_interval (left_col);
496 Slice rs = Note_column::head_positions_interval (right_col);
499 musical_dy[UP] = rs[UP] - ls[UP];
500 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
501 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
503 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
511 *offset = -dir * infinity_f;
513 Item *lgr = get_x_bound_item (me, LEFT, dir);
514 Item *rgr = get_x_bound_item (me, RIGHT, dir);
515 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
516 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
518 Array<Offset> points;
519 points.push (Offset (x0 - x0, staff[dir]));
520 points.push (Offset (x1 - x0, staff[dir]));
522 for (int i = 0; i < columns.size (); i++)
524 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
525 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
527 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
528 points.push (Offset (x, notey));
532 This is a slight hack. We compute two encompass points from the
533 bbox of the smaller tuplets.
535 We assume that the smaller bracket is 1.0 space high.
537 Real ss = Staff_symbol_referencer::staff_space (me);
538 for (int i = 0; i < tuplets.size (); i++)
540 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
541 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
544 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
547 Real other_dy = positions[RIGHT] - positions[LEFT];
552 = tuplet_y.linear_combination (d * sign (other_dy));
556 Let's not take padding into account for nested tuplets.
557 the edges can come very close to the stems, likewise for
560 Drul_array<Real> my_height
561 = robust_scm2drul (me->get_property ("edge-height"),
563 if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
564 == me->get_bound (d))
565 y += dir * my_height[d];
568 points.push (Offset (tuplet_x[d] - x0, y));
570 while (flip (&d) != LEFT);
573 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
574 for (int i = 0; i < points.size (); i++)
576 Real x = points[i][X_AXIS];
577 Real tuplety = (*dy) * x * factor;
579 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
580 *offset = points[i][Y_AXIS] - tuplety;
583 *offset += scm_to_double (me->get_property ("padding")) * dir;
586 horizontal brackets should not collide with staff lines.
588 Kind of pointless since we put them outside the staff anyway, but
589 let's leave code for the future when possibly allow them to move
590 into the staff once again.
593 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
595 // quantize, then do collision check.
598 *offset = rint (*offset);
599 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
607 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
609 Tuplet_bracket::calc_direction (SCM smob)
611 Grob *me = unsmob_grob (smob);
612 Direction dir = Tuplet_bracket::get_default_dir (me);
613 return scm_from_int (dir);
616 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
618 Tuplet_bracket::calc_positions (SCM smob)
620 Grob *me = unsmob_grob (smob);
621 extract_grob_set (me, "note-columns", columns);
623 if (columns.is_empty())
626 return scm_cons (scm_from_double (0),
627 scm_from_double (0));
630 Direction dir = get_grob_direction (me);
631 bool equally_long = false;
632 Grob *par_beam = parallel_beam (me, columns, &equally_long);
635 We follow the beam only if there is one, and we are next to it.
640 || get_grob_direction (par_beam) != dir)
641 calc_position_and_height (me, &offset, &dy);
644 SCM ps = par_beam->get_property ("positions");
646 Real lp = scm_to_double (scm_car (ps));
647 Real rp = scm_to_double (scm_cdr (ps));
652 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
657 SCM x = scm_cons (scm_from_double (offset),
658 scm_from_double (offset + dy));
667 Tuplet_bracket::get_default_dir (Grob *me)
669 Drul_array<int> dirs (0, 0);
670 extract_grob_set (me, "note-columns", columns);
671 for (int i = 0; i < columns.size (); i++)
673 Grob *nc = columns[i];
674 Direction d = Note_column::dir (nc);
679 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
683 Tuplet_bracket::add_column (Grob *me, Item *n)
685 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
686 add_bound_item (dynamic_cast<Spanner *> (me), n);
690 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
692 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
695 ADD_INTERFACE (Tuplet_bracket,
696 "tuplet-bracket-interface",
697 "A bracket with a number in the middle, used for tuplets. "
698 "When the bracket spans a line break, the value of "
699 "@code{break-overshoot} determines how far it extends "
701 "At a line break, the markups in the @code{edge-text} are printed "
707 "bracket-visibility "
709 "connect-to-neighbor "