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 ()));
96 || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
97 != me->get_bound (RIGHT)->get_column ()))
100 Drul_array<Grob*> beams;
103 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
104 } while (flip (&d) != LEFT);
106 *equally_long = false;
107 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
110 extract_grob_set (beams[LEFT], "stems", beam_stems);
111 if (beam_stems.size () == 0)
113 programming_error ("beam under tuplet bracket has no stems");
119 (beam_stems[0] == stems[LEFT]
120 && beam_stems.back () == stems[RIGHT]);
125 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
127 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
129 Spanner *me = unsmob_spanner (smob);
131 Direction dir = get_grob_direction (me);
132 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
133 get_x_bound_item (me, RIGHT, dir));
135 Drul_array<bool> connect_to_other (false, false);
139 Direction break_dir = bounds[d]->break_status_dir ();
141 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
143 vsize neighbor_idx = me->get_break_index () - break_dir;
146 && neighbor_idx < orig_spanner->broken_intos_.size ())
148 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
150 /* trigger possible suicide*/
151 (void) neighbor->get_property ("positions");
156 && neighbor_idx < orig_spanner->broken_intos_.size ()
157 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
159 while (flip (&d) != LEFT);
162 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
163 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
164 scm_from_bool (connect_to_other[RIGHT]));
170 Tuplet_bracket::get_common_x (Spanner *me)
172 extract_grob_set (me, "note-columns", columns);
174 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
175 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
176 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
181 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
183 Tuplet_bracket::calc_control_points (SCM smob)
185 Spanner *me = unsmob_spanner (smob);
187 extract_grob_set (me, "note-columns", columns);
189 SCM scm_positions = me->get_property ("positions");
193 if (!scm_is_pair (scm_positions))
194 programming_error ("Positions should be number pair");
196 Drul_array<Real> positions
197 = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
199 Grob *commonx = get_common_x (me);
200 Direction dir = get_grob_direction (me);
202 Drul_array<Item *> bounds;
203 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
204 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
206 Drul_array<bool> connect_to_other =
207 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
208 Drul_array<bool> (false, false));
215 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
217 if (connect_to_other[d])
219 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
220 Interval (-0.5, 0.0)));
223 x_span[d] += d * overshoot[d];
225 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
231 || (bounds[d]->get_column ()
232 != dynamic_cast<Item *> (columns.back ())->get_column ())))
235 We're connecting to a column, for the last bit of a broken
238 TODO: make padding tunable?
242 if (bounds[d]->break_status_dir ())
246 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
250 while (flip (&d) != LEFT);
254 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
255 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
256 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
262 in the case that there is no bracket, but there is a (single) beam,
263 follow beam precisely for determining tuplet number location.
265 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
267 Tuplet_bracket::print (SCM smob)
269 Spanner *me = unsmob_spanner (smob);
272 extract_grob_set (me, "note-columns", columns);
273 bool equally_long = false;
274 Grob *par_beam = parallel_beam (me, columns, &equally_long);
276 bool bracket_visibility = !(par_beam && equally_long);
278 Fixme: the type of this prop is sucky.
280 SCM bracket = me->get_property ("bracket-visibility");
281 if (scm_is_bool (bracket))
282 bracket_visibility = ly_scm2bool (bracket);
283 else if (bracket == ly_symbol2scm ("if-no-beam"))
284 bracket_visibility = !par_beam;
287 SCM cpoints = me->get_property ("control-points");
288 if (scm_ilength (cpoints) < 2)
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]);
303 Output_def *pap = me->layout ();
305 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
308 No bracket when it would be smaller than the number.
311 if (bracket_visibility && number_grob)
313 Interval ext = number_grob->extent (number_grob, X_AXIS);
314 if (!ext.is_empty ())
316 gap = ext.length () + 1.0;
318 if (0.75 * x_span.length () < gap)
319 bracket_visibility = false;
323 if (bracket_visibility)
325 Drul_array<Real> zero (0, 0);
326 Real ss = Staff_symbol_referencer::staff_space (me);
327 Drul_array<Real> height
328 = robust_scm2drul (me->get_property ("edge-height"), zero);
329 Drul_array<Real> flare
330 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
331 Drul_array<Real> shorten
332 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
333 Drul_array<Stencil> edge_stencils;
335 Direction dir = get_grob_direction (me);
337 scale_drul (&height, -ss * dir);
338 scale_drul (&flare, ss);
339 scale_drul (&shorten, ss);
341 Drul_array<bool> connect_to_other =
342 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
343 Drul_array<bool> (false, false));
348 if (connect_to_other[d])
354 SCM edge_text = me->get_property ("edge-text");
356 if (scm_is_pair (edge_text))
358 SCM properties = Font_interface::text_font_alist_chain (me);
359 SCM text = index_get_cell (edge_text, d);
360 if (Text_interface::is_markup (text))
362 SCM t = Text_interface::interpret_markup (pap->self_scm (),
365 Stencil *edge_text = unsmob_stencil (t);
366 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
367 edge_stencils[d] = *edge_text;
372 while (flip (&d) != LEFT);
374 Stencil brack = make_bracket (me, Y_AXIS,
375 points[RIGHT] - points[LEFT],
378 0.1 = more space at right due to italics
379 TODO: use italic correction of font.
381 Interval (-0.5, 0.5) * gap + 0.1,
386 if (!edge_stencils[d].is_empty ())
387 brack.add_stencil (edge_stencils[d]);
389 while (flip (&d) != LEFT);
391 mol.add_stencil (brack);
394 mol.translate (points[LEFT]);
395 return mol.smobbed_copy ();
399 should move to lookup?
401 TODO: this will fail for very short (shorter than the flare)
405 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
408 Drul_array<Real> height,
410 Drul_array<Real> flare,
411 Drul_array<Real> shorten)
413 Drul_array<Offset> corners (Offset (0, 0), dz);
415 Real length = dz.length ();
416 Drul_array<Offset> gap_corners;
418 Axis bracket_axis = other_axis (protusion_axis);
420 Drul_array<Offset> straight_corners = corners;
424 straight_corners[d] += -d * shorten[d] / length * dz;
425 while (flip (&d) != LEFT);
427 if (!gap.is_empty ())
430 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
431 while (flip (&d) != LEFT);
434 Drul_array<Offset> flare_corners = straight_corners;
437 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
438 flare_corners[d][protusion_axis] += height[d];
439 straight_corners[d][bracket_axis] += -d * flare[d];
441 while (flip (&d) != LEFT);
446 if (!gap.is_empty ())
447 m.add_stencil (Line_interface::line (me, straight_corners[d],
450 m.add_stencil (Line_interface::line (me, straight_corners[d],
454 while (flip (&d) != LEFT);
457 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
458 straight_corners[RIGHT]));
464 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
466 extract_grob_set (me, "note-columns", columns);
468 while (l < columns.size () && Note_column::has_rests (columns[l]))
471 vsize r = columns.size ();
472 while (r > l && Note_column::has_rests (columns[r-1]))
480 *right = columns[r-1];
485 use first -> last note for slope, and then correct for disturbing
488 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
490 Spanner *me = dynamic_cast<Spanner *> (me_grob);
492 extract_grob_set (me, "note-columns", columns);
493 extract_grob_set (me, "tuplets", tuplets);
495 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
496 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
497 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
498 commony = st->common_refpoint (commony, Y_AXIS);
499 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
501 Grob *commonx = get_common_x (me);
502 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
505 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
507 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
510 staff = st->extent (commony, Y_AXIS) - my_offset;
515 Direction dir = get_grob_direction (me);
517 bool equally_long = false;
518 Grob *par_beam = parallel_beam (me, columns, &equally_long);
522 Item *lgr = get_x_bound_item (me, LEFT, dir);
523 Item *rgr = get_x_bound_item (me, RIGHT, dir);
524 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
525 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
526 bool follow_beam = par_beam
527 && ((get_grob_direction (par_beam) == dir) || to_boolean (par_beam->get_property ("knee")));
529 vector<Offset> points;
533 && Note_column::get_stem (columns[0])
534 && Note_column::get_stem (columns.back ()))
537 trigger set_stem_ends
539 (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 ()));
548 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
549 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
550 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
551 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
552 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
555 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
556 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
561 Use outer non-rest columns to determine slope
565 get_bounds (me, &left_col, &right_col);
566 if (left_col && right_col)
568 Interval rv = right_col->extent (commony, Y_AXIS);
569 Interval lv = left_col->extent (commony, Y_AXIS);
572 Real graphical_dy = rv[dir] - lv[dir];
574 Slice ls = Note_column::head_positions_interval (left_col);
575 Slice rs = Note_column::head_positions_interval (right_col);
578 musical_dy[UP] = rs[UP] - ls[UP];
579 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
580 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
582 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
590 for (vsize i = 0; i < columns.size (); i++)
592 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
593 Real notey = note_ext[dir] - my_offset;
595 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
596 points.push_back (Offset (x, notey));
602 points.push_back (Offset (x0 - x0, staff[dir]));
603 points.push_back (Offset (x1 - x0, staff[dir]));
607 This is a slight hack. We compute two encompass points from the
608 bbox of the smaller tuplets.
610 We assume that the smaller bracket is 1.0 space high.
612 Real ss = Staff_symbol_referencer::staff_space (me);
613 for (vsize i = 0; i < tuplets.size (); i++)
615 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
616 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
618 if (!tuplets[i]->is_live ())
622 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
626 Real other_dy = positions[RIGHT] - positions[LEFT];
631 = tuplet_y.linear_combination (d * sign (other_dy));
634 We don't take padding into account for nested tuplets.
635 the edges can come very close to the stems, likewise for
639 points.push_back (Offset (tuplet_x[d] - x0, y));
641 while (flip (&d) != LEFT);
644 *offset = -dir * infinity_f;
645 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
646 for (vsize i = 0; i < points.size (); i++)
648 Real x = points[i][X_AXIS];
649 Real tuplety = (*dy) * x * factor;
651 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
652 *offset = points[i][Y_AXIS] - tuplety;
655 *offset += scm_to_double (me->get_property ("padding")) * dir;
658 horizontal brackets should not collide with staff lines.
660 Kind of pointless since we put them outside the staff anyway, but
661 let's leave code for the future when possibly allow them to move
662 into the staff once again.
664 This doesn't seem to support cross-staff tuplets atm.
667 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
669 // quantize, then do collision check.
672 *offset = rint (*offset);
673 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
681 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
683 Tuplet_bracket::calc_direction (SCM smob)
685 Grob *me = unsmob_grob (smob);
686 Direction dir = Tuplet_bracket::get_default_dir (me);
687 return scm_from_int (dir);
690 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
692 Tuplet_bracket::calc_positions (SCM smob)
694 Spanner *me = unsmob_spanner (smob);
697 Don't print if it doesn't span time.
699 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
700 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
708 calc_position_and_height (me, &offset, &dy);
710 SCM x = scm_cons (scm_from_double (offset),
711 scm_from_double (offset + dy));
720 Tuplet_bracket::get_default_dir (Grob *me)
722 Drul_array<int> dirs (0, 0);
723 extract_grob_set (me, "note-columns", columns);
724 for (vsize i = 0; i < columns.size (); i++)
726 Grob *nc = columns[i];
727 Direction d = Note_column::dir (nc);
732 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
736 Tuplet_bracket::add_column (Grob *me, Item *n)
738 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
739 add_bound_item (dynamic_cast<Spanner *> (me), n);
743 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
745 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
748 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
750 Tuplet_bracket::calc_cross_staff (SCM smob)
752 Grob *me = unsmob_grob (smob);
753 Grob *staff_symbol = 0;
754 extract_grob_set (me, "note-columns", cols);
755 bool equally_long = false;
756 Grob *par_beam = parallel_beam (me, cols, &equally_long);
759 return par_beam->get_property ("cross-staff");
761 for (vsize i = 0; i < cols.size (); i++)
763 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
767 if (to_boolean (stem->get_property ("cross-staff")))
770 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
771 if (staff_symbol && (stem_staff != staff_symbol))
773 staff_symbol = stem_staff;
778 ADD_INTERFACE (Tuplet_bracket,
779 "A bracket with a number in the middle, used for tuplets. "
780 "When the bracket spans a line break, the value of "
781 "@code{break-overshoot} determines how far it extends "
783 "At a line break, the markups in the @code{edge-text} are printed "
789 "bracket-visibility "
791 "connect-to-neighbor "