2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2007 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"
47 #include "paper-column.hh"
51 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
53 Spanner *me = dynamic_cast<Spanner *> (me_grob);
54 Item *g = me->get_bound (hdir);
55 if (Note_column::has_interface (g)
56 && Note_column::get_stem (g)
57 && Note_column::dir (g) == my_dir)
58 g = Note_column::get_stem (g);
65 flatten_number_pair_property (Grob *me,
66 Direction xdir, SCM sym)
68 Drul_array<Real> zero (0, 0);
70 = robust_scm2drul (me->internal_get_property (sym), zero);
73 me->set_property (sym, ly_interval2scm (pair));
78 Return beam that encompasses the span of the tuplet bracket.
81 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
84 Spanner *me = dynamic_cast<Spanner *> (me_grob);
86 if (me->get_bound (LEFT)->break_status_dir ()
87 || me->get_bound (RIGHT)->break_status_dir ())
90 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
91 Note_column::get_stem (cols.back ()));
95 || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
96 != me->get_bound (RIGHT)->get_column ()))
99 Drul_array<Grob*> beams;
102 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
103 } while (flip (&d) != LEFT);
105 *equally_long = false;
106 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
109 extract_grob_set (beams[LEFT], "stems", beam_stems);
110 if (beam_stems.size () == 0)
112 programming_error ("beam under tuplet bracket has no stems");
118 (beam_stems[0] == stems[LEFT]
119 && beam_stems.back () == stems[RIGHT]);
124 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
126 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
128 Spanner *me = unsmob_spanner (smob);
130 Direction dir = get_grob_direction (me);
131 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
132 get_x_bound_item (me, RIGHT, dir));
134 Drul_array<bool> connect_to_other (false, false);
138 Direction break_dir = bounds[d]->break_status_dir ();
139 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
140 vsize neighbor_idx = me->get_break_index () - break_dir;
143 && neighbor_idx < orig_spanner->broken_intos_.size ())
145 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
147 /* trigger possible suicide*/
148 (void) neighbor->get_property ("positions");
153 && neighbor_idx < orig_spanner->broken_intos_.size ()
154 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
156 while (flip (&d) != LEFT);
159 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
160 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
161 scm_from_bool (connect_to_other[RIGHT]));
167 Tuplet_bracket::get_common_x (Spanner *me)
169 extract_grob_set (me, "note-columns", columns);
171 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
172 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
173 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
178 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
180 Tuplet_bracket::calc_control_points (SCM smob)
182 Spanner *me = unsmob_spanner (smob);
184 extract_grob_set (me, "note-columns", columns);
186 SCM scm_positions = me->get_property ("positions");
190 if (!scm_is_pair (scm_positions))
191 programming_error ("Positions should be number pair");
193 Drul_array<Real> positions
194 = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
196 Grob *commonx = get_common_x (me);
197 Direction dir = get_grob_direction (me);
199 Drul_array<Item *> bounds;
200 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
201 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
203 Drul_array<bool> connect_to_other =
204 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
205 Drul_array<bool> (false, false));
212 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
214 if (connect_to_other[d])
216 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
217 Interval (-0.5, 0.0)));
220 x_span[d] += d * overshoot[d];
222 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
228 || (bounds[d]->get_column ()
229 != dynamic_cast<Item *> (columns.back ())->get_column ())))
232 We're connecting to a column, for the last bit of a broken
235 TODO: make padding tunable?
239 if (bounds[d]->break_status_dir ())
243 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
247 while (flip (&d) != LEFT);
251 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
252 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
253 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
259 in the case that there is no bracket, but there is a (single) beam,
260 follow beam precisely for determining tuplet number location.
262 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
264 Tuplet_bracket::print (SCM smob)
266 Spanner *me = unsmob_spanner (smob);
269 extract_grob_set (me, "note-columns", columns);
270 bool equally_long = false;
271 Grob *par_beam = parallel_beam (me, columns, &equally_long);
273 bool bracket_visibility = !(par_beam && equally_long);
275 Fixme: the type of this prop is sucky.
277 SCM bracket = me->get_property ("bracket-visibility");
278 if (scm_is_bool (bracket))
279 bracket_visibility = ly_scm2bool (bracket);
280 else if (bracket == ly_symbol2scm ("if-no-beam"))
281 bracket_visibility = !par_beam;
283 /* Don't print a tuplet bracket if no control-points were calculated or the tuplet
284 does not span any time, i.e. a single-note tuplet */
285 SCM cpoints = me->get_property ("control-points");
286 if (scm_ilength (cpoints) < 2 ||
287 robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
288 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
294 Drul_array<Offset> points;
295 points[LEFT] = ly_scm2offset (scm_car (cpoints));
296 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
298 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
299 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
301 Output_def *pap = me->layout ();
303 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
306 No bracket when it would be smaller than the number.
309 if (bracket_visibility && number_grob)
311 Interval ext = number_grob->extent (number_grob, X_AXIS);
312 if (!ext.is_empty ())
314 gap = ext.length () + 1.0;
316 if (0.75 * x_span.length () < gap)
317 bracket_visibility = false;
321 if (bracket_visibility)
323 Drul_array<Real> zero (0, 0);
324 Real ss = Staff_symbol_referencer::staff_space (me);
325 Drul_array<Real> height
326 = robust_scm2drul (me->get_property ("edge-height"), zero);
327 Drul_array<Real> flare
328 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
329 Drul_array<Real> shorten
330 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
331 Drul_array<Stencil> edge_stencils;
333 Direction dir = get_grob_direction (me);
335 scale_drul (&height, -ss * dir);
336 scale_drul (&flare, ss);
337 scale_drul (&shorten, ss);
339 Drul_array<bool> connect_to_other =
340 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
341 Drul_array<bool> (false, false));
346 if (connect_to_other[d])
352 SCM edge_text = me->get_property ("edge-text");
354 if (scm_is_pair (edge_text))
356 SCM properties = Font_interface::text_font_alist_chain (me);
357 SCM text = index_get_cell (edge_text, d);
358 if (Text_interface::is_markup (text))
360 SCM t = Text_interface::interpret_markup (pap->self_scm (),
363 Stencil *edge_text = unsmob_stencil (t);
364 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
365 edge_stencils[d] = *edge_text;
370 while (flip (&d) != LEFT);
372 Stencil brack = make_bracket (me, Y_AXIS,
373 points[RIGHT] - points[LEFT],
376 0.1 = more space at right due to italics
377 TODO: use italic correction of font.
379 Interval (-0.5, 0.5) * gap + 0.1,
384 if (!edge_stencils[d].is_empty ())
385 brack.add_stencil (edge_stencils[d]);
387 while (flip (&d) != LEFT);
389 mol.add_stencil (brack);
392 mol.translate (points[LEFT]);
393 return mol.smobbed_copy ();
397 should move to lookup?
399 TODO: this will fail for very short (shorter than the flare)
403 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
406 Drul_array<Real> height,
408 Drul_array<Real> flare,
409 Drul_array<Real> shorten)
411 Drul_array<Offset> corners (Offset (0, 0), dz);
413 Real length = dz.length ();
414 Drul_array<Offset> gap_corners;
416 Axis bracket_axis = other_axis (protusion_axis);
418 Drul_array<Offset> straight_corners = corners;
422 straight_corners[d] += -d * shorten[d] / length * dz;
423 while (flip (&d) != LEFT);
425 if (!gap.is_empty ())
428 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
429 while (flip (&d) != LEFT);
432 Drul_array<Offset> flare_corners = straight_corners;
435 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
436 flare_corners[d][protusion_axis] += height[d];
437 straight_corners[d][bracket_axis] += -d * flare[d];
439 while (flip (&d) != LEFT);
444 if (!gap.is_empty ())
445 m.add_stencil (Line_interface::line (me, straight_corners[d],
448 m.add_stencil (Line_interface::line (me, straight_corners[d],
452 while (flip (&d) != LEFT);
455 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
456 straight_corners[RIGHT]));
462 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
464 extract_grob_set (me, "note-columns", columns);
466 while (l < columns.size () && Note_column::has_rests (columns[l]))
469 vsize r = columns.size ();
470 while (r > l && Note_column::has_rests (columns[r-1]))
478 *right = columns[r-1];
483 use first -> last note for slope, and then correct for disturbing
486 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
488 Spanner *me = dynamic_cast<Spanner *> (me_grob);
490 extract_grob_set (me, "note-columns", columns);
491 extract_grob_set (me, "tuplets", tuplets);
493 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
494 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
495 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
496 commony = st->common_refpoint (commony, Y_AXIS);
497 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
499 Grob *commonx = get_common_x (me);
500 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
503 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
505 /* staff-padding doesn't work correctly on cross-staff tuplets
506 because it only considers one staff symbol. Until this works,
508 if (st && !to_boolean (me->get_property ("cross-staff")))
510 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
513 staff = st->extent (commony, Y_AXIS) - my_offset;
518 Direction dir = get_grob_direction (me);
520 bool equally_long = false;
521 Grob *par_beam = parallel_beam (me, columns, &equally_long);
523 Item *lgr = get_x_bound_item (me, LEFT, dir);
524 Item *rgr = get_x_bound_item (me, RIGHT, dir);
525 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
526 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
527 bool follow_beam = par_beam
528 && get_grob_direction (par_beam) == dir
529 && ! to_boolean (par_beam->get_property ("knee"));
531 vector<Offset> points;
534 && Note_column::get_stem (columns[0])
535 && Note_column::get_stem (columns.back ()))
538 trigger set_stem_ends
540 (void) par_beam->get_property ("quantized-positions");
542 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
543 Note_column::get_stem (columns.back ()));
545 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
546 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
547 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
548 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
549 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
552 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
553 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
558 Use outer non-rest columns to determine slope
562 get_bounds (me, &left_col, &right_col);
563 if (left_col && right_col)
565 Interval rv = Note_column::cross_staff_extent (right_col, commony);
566 Interval lv = Note_column::cross_staff_extent (left_col, commony);
570 Real graphical_dy = rv[dir] - lv[dir];
572 Slice ls = Note_column::head_positions_interval (left_col);
573 Slice rs = Note_column::head_positions_interval (right_col);
576 musical_dy[UP] = rs[UP] - ls[UP];
577 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
578 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
580 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
588 for (vsize i = 0; i < columns.size (); i++)
590 Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
591 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
593 points.push_back (Offset (x, note_ext[dir]));
599 points.push_back (Offset (x0 - x0, staff[dir]));
600 points.push_back (Offset (x1 - x0, staff[dir]));
604 This is a slight hack. We compute two encompass points from the
605 bbox of the smaller tuplets.
607 We assume that the smaller bracket is 1.0 space high.
609 Real ss = Staff_symbol_referencer::staff_space (me);
610 for (vsize i = 0; i < tuplets.size (); i++)
612 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
613 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
615 if (!tuplets[i]->is_live ())
619 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
623 Real other_dy = positions[RIGHT] - positions[LEFT];
628 = tuplet_y.linear_combination (d * sign (other_dy));
631 We don't take padding into account for nested tuplets.
632 the edges can come very close to the stems, likewise for
636 points.push_back (Offset (tuplet_x[d] - x0, y));
638 while (flip (&d) != LEFT);
641 *offset = -dir * infinity_f;
642 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
643 for (vsize i = 0; i < points.size (); i++)
645 Real x = points[i][X_AXIS];
646 Real tuplety = (*dy) * x * factor + my_offset;
648 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
649 *offset = points[i][Y_AXIS] - tuplety;
652 *offset += scm_to_double (me->get_property ("padding")) * dir;
655 horizontal brackets should not collide with staff lines.
657 Kind of pointless since we put them outside the staff anyway, but
658 let's leave code for the future when possibly allow them to move
659 into the staff once again.
661 This doesn't seem to support cross-staff tuplets atm.
664 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
666 // quantize, then do collision check.
669 *offset = rint (*offset);
670 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
678 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
680 Tuplet_bracket::calc_direction (SCM smob)
682 Grob *me = unsmob_grob (smob);
683 Direction dir = Tuplet_bracket::get_default_dir (me);
684 return scm_from_int (dir);
687 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
689 Tuplet_bracket::calc_positions (SCM smob)
691 Spanner *me = unsmob_spanner (smob);
695 calc_position_and_height (me, &offset, &dy);
697 SCM x = scm_cons (scm_from_double (offset),
698 scm_from_double (offset + dy));
707 Tuplet_bracket::get_default_dir (Grob *me)
709 Drul_array<int> dirs (0, 0);
710 extract_grob_set (me, "note-columns", columns);
711 for (vsize i = 0; i < columns.size (); i++)
713 Grob *nc = columns[i];
714 Direction d = Note_column::dir (nc);
719 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
723 Tuplet_bracket::add_column (Grob *me, Item *n)
725 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
726 add_bound_item (dynamic_cast<Spanner *> (me), n);
730 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
732 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
735 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
737 Tuplet_bracket::calc_cross_staff (SCM smob)
739 Grob *me = unsmob_grob (smob);
740 Grob *staff_symbol = 0;
741 extract_grob_set (me, "note-columns", cols);
742 bool equally_long = false;
743 Grob *par_beam = parallel_beam (me, cols, &equally_long);
746 return par_beam->get_property ("cross-staff");
748 for (vsize i = 0; i < cols.size (); i++)
750 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
754 if (to_boolean (stem->get_property ("cross-staff")))
757 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
758 if (staff_symbol && (stem_staff != staff_symbol))
760 staff_symbol = stem_staff;
765 ADD_INTERFACE (Tuplet_bracket,
766 "A bracket with a number in the middle, used for tuplets. "
767 "When the bracket spans a line break, the value of "
768 "@code{break-overshoot} determines how far it extends "
770 "At a line break, the markups in the @code{edge-text} are printed "
776 "bracket-visibility "
778 "connect-to-neighbor "