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 SCM cpoints = me->get_property ("control-points");
284 if (scm_ilength (cpoints) < 2)
290 Drul_array<Offset> points;
291 points[LEFT] = ly_scm2offset (scm_car (cpoints));
292 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
294 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
295 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
297 Output_def *pap = me->layout ();
299 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
302 No bracket when it would be smaller than the number.
305 if (bracket_visibility && number_grob)
307 Interval ext = number_grob->extent (number_grob, X_AXIS);
308 if (!ext.is_empty ())
310 gap = ext.length () + 1.0;
312 if (0.75 * x_span.length () < gap)
313 bracket_visibility = false;
317 if (bracket_visibility)
319 Drul_array<Real> zero (0, 0);
320 Real ss = Staff_symbol_referencer::staff_space (me);
321 Drul_array<Real> height
322 = robust_scm2drul (me->get_property ("edge-height"), zero);
323 Drul_array<Real> flare
324 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
325 Drul_array<Real> shorten
326 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
327 Drul_array<Stencil> edge_stencils;
329 Direction dir = get_grob_direction (me);
331 scale_drul (&height, -ss * dir);
332 scale_drul (&flare, ss);
333 scale_drul (&shorten, ss);
335 Drul_array<bool> connect_to_other =
336 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
337 Drul_array<bool> (false, false));
342 if (connect_to_other[d])
348 SCM edge_text = me->get_property ("edge-text");
350 if (scm_is_pair (edge_text))
352 SCM properties = Font_interface::text_font_alist_chain (me);
353 SCM text = index_get_cell (edge_text, d);
354 if (Text_interface::is_markup (text))
356 SCM t = Text_interface::interpret_markup (pap->self_scm (),
359 Stencil *edge_text = unsmob_stencil (t);
360 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
361 edge_stencils[d] = *edge_text;
366 while (flip (&d) != LEFT);
368 Stencil brack = make_bracket (me, Y_AXIS,
369 points[RIGHT] - points[LEFT],
372 0.1 = more space at right due to italics
373 TODO: use italic correction of font.
375 Interval (-0.5, 0.5) * gap + 0.1,
380 if (!edge_stencils[d].is_empty ())
381 brack.add_stencil (edge_stencils[d]);
383 while (flip (&d) != LEFT);
385 mol.add_stencil (brack);
388 mol.translate (points[LEFT]);
389 return mol.smobbed_copy ();
393 should move to lookup?
395 TODO: this will fail for very short (shorter than the flare)
399 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
402 Drul_array<Real> height,
404 Drul_array<Real> flare,
405 Drul_array<Real> shorten)
407 Drul_array<Offset> corners (Offset (0, 0), dz);
409 Real length = dz.length ();
410 Drul_array<Offset> gap_corners;
412 Axis bracket_axis = other_axis (protusion_axis);
414 Drul_array<Offset> straight_corners = corners;
418 straight_corners[d] += -d * shorten[d] / length * dz;
419 while (flip (&d) != LEFT);
421 if (!gap.is_empty ())
424 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
425 while (flip (&d) != LEFT);
428 Drul_array<Offset> flare_corners = straight_corners;
431 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
432 flare_corners[d][protusion_axis] += height[d];
433 straight_corners[d][bracket_axis] += -d * flare[d];
435 while (flip (&d) != LEFT);
440 if (!gap.is_empty ())
441 m.add_stencil (Line_interface::line (me, straight_corners[d],
444 m.add_stencil (Line_interface::line (me, straight_corners[d],
448 while (flip (&d) != LEFT);
451 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
452 straight_corners[RIGHT]));
458 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
460 extract_grob_set (me, "note-columns", columns);
462 while (l < columns.size () && Note_column::has_rests (columns[l]))
465 vsize r = columns.size ();
466 while (r > l && Note_column::has_rests (columns[r-1]))
474 *right = columns[r-1];
479 use first -> last note for slope, and then correct for disturbing
482 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
484 Spanner *me = dynamic_cast<Spanner *> (me_grob);
486 extract_grob_set (me, "note-columns", columns);
487 extract_grob_set (me, "tuplets", tuplets);
489 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
490 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
491 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
492 commony = st->common_refpoint (commony, Y_AXIS);
493 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
495 Grob *commonx = get_common_x (me);
496 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
499 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
501 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
504 staff = st->extent (commony, Y_AXIS) - my_offset;
509 Direction dir = get_grob_direction (me);
511 bool equally_long = false;
512 Grob *par_beam = parallel_beam (me, columns, &equally_long);
514 Item *lgr = get_x_bound_item (me, LEFT, dir);
515 Item *rgr = get_x_bound_item (me, RIGHT, dir);
516 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
517 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
518 bool follow_beam = par_beam
519 && ((get_grob_direction (par_beam) == dir)
520 || to_boolean (par_beam->get_property ("knee")));
522 vector<Offset> points;
525 && Note_column::get_stem (columns[0])
526 && Note_column::get_stem (columns.back ()))
529 trigger set_stem_ends
531 (void) par_beam->get_property ("quantized-positions");
533 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
534 Note_column::get_stem (columns.back ()));
536 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
537 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
538 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
539 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
540 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
543 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
544 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
549 Use outer non-rest columns to determine slope
553 get_bounds (me, &left_col, &right_col);
554 if (left_col && right_col)
556 Interval rv = right_col->extent (commony, Y_AXIS);
557 Interval lv = left_col->extent (commony, Y_AXIS);
560 Real graphical_dy = rv[dir] - lv[dir];
562 Slice ls = Note_column::head_positions_interval (left_col);
563 Slice rs = Note_column::head_positions_interval (right_col);
566 musical_dy[UP] = rs[UP] - ls[UP];
567 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
568 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
570 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
578 for (vsize i = 0; i < columns.size (); i++)
580 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
581 Real notey = note_ext[dir] - my_offset;
583 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
584 points.push_back (Offset (x, notey));
590 points.push_back (Offset (x0 - x0, staff[dir]));
591 points.push_back (Offset (x1 - x0, staff[dir]));
595 This is a slight hack. We compute two encompass points from the
596 bbox of the smaller tuplets.
598 We assume that the smaller bracket is 1.0 space high.
600 Real ss = Staff_symbol_referencer::staff_space (me);
601 for (vsize i = 0; i < tuplets.size (); i++)
603 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
604 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
606 if (!tuplets[i]->is_live ())
610 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
614 Real other_dy = positions[RIGHT] - positions[LEFT];
619 = tuplet_y.linear_combination (d * sign (other_dy));
622 We don't take padding into account for nested tuplets.
623 the edges can come very close to the stems, likewise for
627 points.push_back (Offset (tuplet_x[d] - x0, y));
629 while (flip (&d) != LEFT);
632 *offset = -dir * infinity_f;
633 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
634 for (vsize i = 0; i < points.size (); i++)
636 Real x = points[i][X_AXIS];
637 Real tuplety = (*dy) * x * factor;
639 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
640 *offset = points[i][Y_AXIS] - tuplety;
643 *offset += scm_to_double (me->get_property ("padding")) * dir;
646 horizontal brackets should not collide with staff lines.
648 Kind of pointless since we put them outside the staff anyway, but
649 let's leave code for the future when possibly allow them to move
650 into the staff once again.
652 This doesn't seem to support cross-staff tuplets atm.
655 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
657 // quantize, then do collision check.
660 *offset = rint (*offset);
661 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
669 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
671 Tuplet_bracket::calc_direction (SCM smob)
673 Grob *me = unsmob_grob (smob);
674 Direction dir = Tuplet_bracket::get_default_dir (me);
675 return scm_from_int (dir);
678 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
680 Tuplet_bracket::calc_positions (SCM smob)
682 Spanner *me = unsmob_spanner (smob);
685 Don't print if it doesn't span time.
687 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
688 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
696 calc_position_and_height (me, &offset, &dy);
698 SCM x = scm_cons (scm_from_double (offset),
699 scm_from_double (offset + dy));
708 Tuplet_bracket::get_default_dir (Grob *me)
710 Drul_array<int> dirs (0, 0);
711 extract_grob_set (me, "note-columns", columns);
712 for (vsize i = 0; i < columns.size (); i++)
714 Grob *nc = columns[i];
715 Direction d = Note_column::dir (nc);
720 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
724 Tuplet_bracket::add_column (Grob *me, Item *n)
726 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
727 add_bound_item (dynamic_cast<Spanner *> (me), n);
731 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
733 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
736 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
738 Tuplet_bracket::calc_cross_staff (SCM smob)
740 Grob *me = unsmob_grob (smob);
741 Grob *staff_symbol = 0;
742 extract_grob_set (me, "note-columns", cols);
743 bool equally_long = false;
744 Grob *par_beam = parallel_beam (me, cols, &equally_long);
747 return par_beam->get_property ("cross-staff");
749 for (vsize i = 0; i < cols.size (); i++)
751 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
755 if (to_boolean (stem->get_property ("cross-staff")))
758 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
759 if (staff_symbol && (stem_staff != staff_symbol))
761 staff_symbol = stem_staff;
766 ADD_INTERFACE (Tuplet_bracket,
767 "A bracket with a number in the middle, used for tuplets. "
768 "When the bracket spans a line break, the value of "
769 "@code{break-overshoot} determines how far it extends "
771 "At a line break, the markups in the @code{edge-text} are printed "
777 "bracket-visibility "
779 "connect-to-neighbor "