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 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
501 /* staff-padding doesn't work correctly on cross-staff tuplets
502 because it only considers one staff symbol. Until this works,
504 if (st && !to_boolean (me->get_property ("cross-staff")))
506 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
509 staff = st->extent (commony, Y_AXIS) - my_offset;
514 Direction dir = get_grob_direction (me);
516 bool equally_long = false;
517 Grob *par_beam = parallel_beam (me, columns, &equally_long);
519 Item *lgr = get_x_bound_item (me, LEFT, dir);
520 Item *rgr = get_x_bound_item (me, RIGHT, dir);
521 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
522 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
523 bool follow_beam = par_beam
524 && get_grob_direction (par_beam) == dir
525 && ! to_boolean (par_beam->get_property ("knee"));
527 vector<Offset> points;
530 && Note_column::get_stem (columns[0])
531 && Note_column::get_stem (columns.back ()))
534 trigger set_stem_ends
536 (void) par_beam->get_property ("quantized-positions");
538 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
539 Note_column::get_stem (columns.back ()));
541 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
542 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
543 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
544 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
545 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
548 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
549 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
554 Use outer non-rest columns to determine slope
558 get_bounds (me, &left_col, &right_col);
559 if (left_col && right_col)
561 Interval rv = Note_column::cross_staff_extent (right_col, commony);
562 Interval lv = Note_column::cross_staff_extent (left_col, commony);
566 Real graphical_dy = rv[dir] - lv[dir];
568 Slice ls = Note_column::head_positions_interval (left_col);
569 Slice rs = Note_column::head_positions_interval (right_col);
572 musical_dy[UP] = rs[UP] - ls[UP];
573 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
574 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
576 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
584 for (vsize i = 0; i < columns.size (); i++)
586 Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
587 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
589 points.push_back (Offset (x, note_ext[dir]));
595 points.push_back (Offset (x0 - x0, staff[dir]));
596 points.push_back (Offset (x1 - x0, staff[dir]));
600 This is a slight hack. We compute two encompass points from the
601 bbox of the smaller tuplets.
603 We assume that the smaller bracket is 1.0 space high.
605 Real ss = Staff_symbol_referencer::staff_space (me);
606 for (vsize i = 0; i < tuplets.size (); i++)
608 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
609 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
611 if (!tuplets[i]->is_live ())
615 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
619 Real other_dy = positions[RIGHT] - positions[LEFT];
624 = tuplet_y.linear_combination (d * sign (other_dy));
627 We don't take padding into account for nested tuplets.
628 the edges can come very close to the stems, likewise for
632 points.push_back (Offset (tuplet_x[d] - x0, y));
634 while (flip (&d) != LEFT);
637 *offset = -dir * infinity_f;
638 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
639 for (vsize i = 0; i < points.size (); i++)
641 Real x = points[i][X_AXIS];
642 Real tuplety = (*dy) * x * factor + my_offset;
644 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
645 *offset = points[i][Y_AXIS] - tuplety;
648 *offset += scm_to_double (me->get_property ("padding")) * dir;
651 horizontal brackets should not collide with staff lines.
653 Kind of pointless since we put them outside the staff anyway, but
654 let's leave code for the future when possibly allow them to move
655 into the staff once again.
657 This doesn't seem to support cross-staff tuplets atm.
660 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
662 // quantize, then do collision check.
665 *offset = rint (*offset);
666 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
674 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
676 Tuplet_bracket::calc_direction (SCM smob)
678 Grob *me = unsmob_grob (smob);
679 Direction dir = Tuplet_bracket::get_default_dir (me);
680 return scm_from_int (dir);
683 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
685 Tuplet_bracket::calc_positions (SCM smob)
687 Spanner *me = unsmob_spanner (smob);
690 Don't print if it doesn't span time.
692 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
693 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
701 calc_position_and_height (me, &offset, &dy);
703 SCM x = scm_cons (scm_from_double (offset),
704 scm_from_double (offset + dy));
713 Tuplet_bracket::get_default_dir (Grob *me)
715 Drul_array<int> dirs (0, 0);
716 extract_grob_set (me, "note-columns", columns);
717 for (vsize i = 0; i < columns.size (); i++)
719 Grob *nc = columns[i];
720 Direction d = Note_column::dir (nc);
725 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
729 Tuplet_bracket::add_column (Grob *me, Item *n)
731 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
732 add_bound_item (dynamic_cast<Spanner *> (me), n);
736 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
738 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
741 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
743 Tuplet_bracket::calc_cross_staff (SCM smob)
745 Grob *me = unsmob_grob (smob);
746 Grob *staff_symbol = 0;
747 extract_grob_set (me, "note-columns", cols);
748 bool equally_long = false;
749 Grob *par_beam = parallel_beam (me, cols, &equally_long);
752 return par_beam->get_property ("cross-staff");
754 for (vsize i = 0; i < cols.size (); i++)
756 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
760 if (to_boolean (stem->get_property ("cross-staff")))
763 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
764 if (staff_symbol && (stem_staff != staff_symbol))
766 staff_symbol = stem_staff;
771 ADD_INTERFACE (Tuplet_bracket,
772 "A bracket with a number in the middle, used for tuplets. "
773 "When the bracket spans a line break, the value of "
774 "@code{break-overshoot} determines how far it extends "
776 "At a line break, the markups in the @code{edge-text} are printed "
782 "bracket-visibility "
784 "connect-to-neighbor "