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.
82 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
85 Spanner *me = dynamic_cast<Spanner *> (me_grob);
87 if (me->get_bound (LEFT)->break_status_dir ()
88 || me->get_bound (RIGHT)->break_status_dir ())
91 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
92 Note_column::get_stem (cols.back ()));
94 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
95 != me->get_bound (RIGHT)->get_column ())
98 Drul_array<Grob*> beams;
101 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
102 } while (flip (&d) != LEFT);
104 *equally_long = false;
105 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
108 extract_grob_set (beams[LEFT], "stems", beam_stems);
109 if (beam_stems.size () == 0)
111 programming_error ("beam under tuplet bracket has no stems");
117 (beam_stems[0] == stems[LEFT]
118 && beam_stems.back () == stems[RIGHT]);
123 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
125 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
127 Spanner *me = unsmob_spanner (smob);
129 Direction dir = get_grob_direction (me);
130 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
131 get_x_bound_item (me, RIGHT, dir));
133 Drul_array<bool> connect_to_other (false, false);
137 Direction break_dir = bounds[d]->break_status_dir ();
139 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
141 vsize neighbor_idx = me->get_break_index () - break_dir;
144 && neighbor_idx < orig_spanner->broken_intos_.size ())
146 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
148 /* trigger possible suicide*/
149 (void) neighbor->get_property ("positions");
154 && neighbor_idx < orig_spanner->broken_intos_.size ()
155 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
157 while (flip (&d) != LEFT);
160 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
161 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
162 scm_from_bool (connect_to_other[RIGHT]));
168 Tuplet_bracket::get_common_x (Spanner *me)
170 extract_grob_set (me, "note-columns", columns);
172 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
173 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
174 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
179 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
181 Tuplet_bracket::calc_control_points (SCM smob)
183 Spanner *me = unsmob_spanner (smob);
185 extract_grob_set (me, "note-columns", columns);
187 SCM scm_positions = me->get_property ("positions");
191 if (!scm_is_pair (scm_positions))
192 programming_error ("Positions should be number pair");
194 Drul_array<Real> positions
195 = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
197 Grob *commonx = get_common_x (me);
198 Direction dir = get_grob_direction (me);
200 Drul_array<Item *> bounds;
201 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
202 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
204 Drul_array<bool> connect_to_other =
205 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
206 Drul_array<bool> (false, false));
213 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
215 if (connect_to_other[d])
217 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
218 Interval (-0.5, 0.0)));
221 x_span[d] += d * overshoot[d];
223 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
229 || (bounds[d]->get_column ()
230 != dynamic_cast<Item *> (columns.back ())->get_column ())))
233 We're connecting to a column, for the last bit of a broken
236 TODO: make padding tunable?
240 if (bounds[d]->break_status_dir ())
244 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
248 while (flip (&d) != LEFT);
252 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
253 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
254 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
260 in the case that there is no bracket, but there is a (single) beam,
261 follow beam precisely for determining tuplet number location.
263 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
265 Tuplet_bracket::print (SCM smob)
267 Spanner *me = unsmob_spanner (smob);
270 extract_grob_set (me, "note-columns", columns);
271 bool equally_long = false;
272 Grob *par_beam = parallel_beam (me, columns, &equally_long);
274 bool bracket_visibility = !(par_beam && equally_long);
276 Fixme: the type of this prop is sucky.
278 SCM bracket = me->get_property ("bracket-visibility");
279 if (scm_is_bool (bracket))
280 bracket_visibility = ly_scm2bool (bracket);
281 else if (bracket == ly_symbol2scm ("if-no-beam"))
282 bracket_visibility = !par_beam;
285 SCM cpoints = me->get_property ("control-points");
286 if (scm_ilength (cpoints) < 2)
292 Drul_array<Offset> points;
293 points[LEFT] = ly_scm2offset (scm_car (cpoints));
294 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
296 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
297 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 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
505 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
508 staff = st->extent (commony, Y_AXIS) - my_offset;
513 Direction dir = get_grob_direction (me);
515 bool equally_long = false;
516 Grob *par_beam = parallel_beam (me, columns, &equally_long);
520 Item *lgr = get_x_bound_item (me, LEFT, dir);
521 Item *rgr = get_x_bound_item (me, RIGHT, dir);
522 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
523 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
524 bool follow_beam = par_beam
525 && ((get_grob_direction (par_beam) == dir) || to_boolean (par_beam->get_property ("knee")));
527 vector<Offset> points;
531 && Note_column::get_stem (columns[0])
532 && Note_column::get_stem (columns.back ()))
535 trigger set_stem_ends
537 (void) par_beam->get_property ("quantized-positions");
540 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
541 Note_column::get_stem (columns.back ()));
546 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
547 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
548 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
549 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
550 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
553 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
554 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
559 Use outer non-rest columns to determine slope
563 get_bounds (me, &left_col, &right_col);
564 if (left_col && right_col)
566 Interval rv = right_col->extent (commony, Y_AXIS);
567 Interval lv = left_col->extent (commony, Y_AXIS);
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 = columns[i]->extent (commony, Y_AXIS);
591 Real notey = note_ext[dir] - my_offset;
593 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
594 points.push_back (Offset (x, notey));
600 points.push_back (Offset (x0 - x0, staff[dir]));
601 points.push_back (Offset (x1 - x0, staff[dir]));
605 This is a slight hack. We compute two encompass points from the
606 bbox of the smaller tuplets.
608 We assume that the smaller bracket is 1.0 space high.
610 Real ss = Staff_symbol_referencer::staff_space (me);
611 for (vsize i = 0; i < tuplets.size (); i++)
613 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
614 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
616 if (!tuplets[i]->is_live ())
620 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
624 Real other_dy = positions[RIGHT] - positions[LEFT];
629 = tuplet_y.linear_combination (d * sign (other_dy));
632 We don't take padding into account for nested tuplets.
633 the edges can come very close to the stems, likewise for
637 points.push_back (Offset (tuplet_x[d] - x0, y));
639 while (flip (&d) != LEFT);
642 *offset = -dir * infinity_f;
643 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
644 for (vsize i = 0; i < points.size (); i++)
646 Real x = points[i][X_AXIS];
647 Real tuplety = (*dy) * x * factor;
649 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
650 *offset = points[i][Y_AXIS] - tuplety;
653 *offset += scm_to_double (me->get_property ("padding")) * dir;
656 horizontal brackets should not collide with staff lines.
658 Kind of pointless since we put them outside the staff anyway, but
659 let's leave code for the future when possibly allow them to move
660 into the staff once again.
662 This doesn't seem to support cross-staff tuplets atm.
665 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
667 // quantize, then do collision check.
670 *offset = rint (*offset);
671 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
679 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
681 Tuplet_bracket::calc_direction (SCM smob)
683 Grob *me = unsmob_grob (smob);
684 Direction dir = Tuplet_bracket::get_default_dir (me);
685 return scm_from_int (dir);
688 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
690 Tuplet_bracket::calc_positions (SCM smob)
692 Spanner *me = unsmob_spanner (smob);
695 Don't print if it doesn't span time.
697 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
698 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
706 calc_position_and_height (me, &offset, &dy);
708 SCM x = scm_cons (scm_from_double (offset),
709 scm_from_double (offset + dy));
718 Tuplet_bracket::get_default_dir (Grob *me)
720 Drul_array<int> dirs (0, 0);
721 extract_grob_set (me, "note-columns", columns);
722 for (vsize i = 0; i < columns.size (); i++)
724 Grob *nc = columns[i];
725 Direction d = Note_column::dir (nc);
730 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
734 Tuplet_bracket::add_column (Grob *me, Item *n)
736 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
737 add_bound_item (dynamic_cast<Spanner *> (me), n);
741 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
743 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
746 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
748 Tuplet_bracket::calc_cross_staff (SCM smob)
750 Grob *me = unsmob_grob (smob);
751 Grob *staff_symbol = 0;
752 extract_grob_set (me, "note-columns", cols);
753 bool equally_long = false;
754 Grob *par_beam = parallel_beam (me, cols, &equally_long);
757 return par_beam->get_property ("cross-staff");
759 for (vsize i = 0; i < cols.size (); i++)
761 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
762 if (to_boolean (stem->get_property ("cross-staff")))
765 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
766 if (staff_symbol && (stem_staff != staff_symbol))
768 staff_symbol = stem_staff;
773 ADD_INTERFACE (Tuplet_bracket,
774 "A bracket with a number in the middle, used for tuplets. "
775 "When the bracket spans a line break, the value of "
776 "@code{break-overshoot} determines how far it extends "
778 "At a line break, the markups in the @code{edge-text} are printed "
784 "bracket-visibility "
786 "connect-to-neighbor "