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
236 robust_scm2double(me->get_property("full-length-padding"), 1.0);
238 if (bounds[d]->break_status_dir ())
241 x_span[d] = bounds[d]->relative_coordinate(commonx, X_AXIS) - padding;
244 while (flip (&d) != LEFT);
248 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
249 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
250 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
256 in the case that there is no bracket, but there is a (single) beam,
257 follow beam precisely for determining tuplet number location.
259 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
261 Tuplet_bracket::print (SCM smob)
263 Spanner *me = unsmob_spanner (smob);
266 extract_grob_set (me, "note-columns", columns);
267 bool equally_long = false;
268 Grob *par_beam = parallel_beam (me, columns, &equally_long);
270 bool bracket_visibility = !(par_beam && equally_long);
272 Fixme: the type of this prop is sucky.
274 SCM bracket = me->get_property ("bracket-visibility");
275 if (scm_is_bool (bracket))
276 bracket_visibility = ly_scm2bool (bracket);
277 else if (bracket == ly_symbol2scm ("if-no-beam"))
278 bracket_visibility = !par_beam;
280 /* Don't print a tuplet bracket and number if no control-points were calculated */
281 SCM cpoints = me->get_property ("control-points");
282 if (scm_ilength (cpoints) < 2)
287 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
288 the bracket, but still let the number be displayed */
289 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
290 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
292 bracket_visibility = false;
295 Drul_array<Offset> points;
296 points[LEFT] = ly_scm2offset (scm_car (cpoints));
297 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
299 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
300 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
302 Output_def *pap = me->layout ();
304 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
307 No bracket when it would be smaller than the number.
310 if (bracket_visibility && number_grob)
312 Interval ext = number_grob->extent (number_grob, X_AXIS);
313 if (!ext.is_empty ())
315 gap = ext.length () + 1.0;
317 if (0.75 * x_span.length () < gap)
318 bracket_visibility = false;
322 if (bracket_visibility)
324 Drul_array<Real> zero (0, 0);
325 Real ss = Staff_symbol_referencer::staff_space (me);
326 Drul_array<Real> height
327 = robust_scm2drul (me->get_property ("edge-height"), zero);
328 Drul_array<Real> flare
329 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
330 Drul_array<Real> shorten
331 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
332 Drul_array<Stencil> edge_stencils;
334 Direction dir = get_grob_direction (me);
336 scale_drul (&height, -ss * dir);
337 scale_drul (&flare, ss);
338 scale_drul (&shorten, ss);
340 Drul_array<bool> connect_to_other =
341 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
342 Drul_array<bool> (false, false));
347 if (connect_to_other[d])
353 SCM edge_text = me->get_property ("edge-text");
355 if (scm_is_pair (edge_text))
357 SCM properties = Font_interface::text_font_alist_chain (me);
358 SCM text = index_get_cell (edge_text, d);
359 if (Text_interface::is_markup (text))
361 SCM t = Text_interface::interpret_markup (pap->self_scm (),
364 Stencil *edge_text = unsmob_stencil (t);
365 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
366 edge_stencils[d] = *edge_text;
371 while (flip (&d) != LEFT);
373 Stencil brack = make_bracket (me, Y_AXIS,
374 points[RIGHT] - points[LEFT],
377 0.1 = more space at right due to italics
378 TODO: use italic correction of font.
380 Interval (-0.5, 0.5) * gap + 0.1,
385 if (!edge_stencils[d].is_empty ())
386 brack.add_stencil (edge_stencils[d]);
388 while (flip (&d) != LEFT);
390 mol.add_stencil (brack);
393 mol.translate (points[LEFT]);
394 return mol.smobbed_copy ();
398 should move to lookup?
400 TODO: this will fail for very short (shorter than the flare)
404 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
407 Drul_array<Real> height,
409 Drul_array<Real> flare,
410 Drul_array<Real> shorten)
412 Drul_array<Offset> corners (Offset (0, 0), dz);
414 Real length = dz.length ();
415 Drul_array<Offset> gap_corners;
417 Axis bracket_axis = other_axis (protusion_axis);
419 Drul_array<Offset> straight_corners = corners;
423 straight_corners[d] += -d * shorten[d] / length * dz;
424 while (flip (&d) != LEFT);
426 if (!gap.is_empty ())
429 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
430 while (flip (&d) != LEFT);
433 Drul_array<Offset> flare_corners = straight_corners;
436 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
437 flare_corners[d][protusion_axis] += height[d];
438 straight_corners[d][bracket_axis] += -d * flare[d];
440 while (flip (&d) != LEFT);
445 if (!gap.is_empty ())
446 m.add_stencil (Line_interface::line (me, straight_corners[d],
449 m.add_stencil (Line_interface::line (me, straight_corners[d],
453 while (flip (&d) != LEFT);
456 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
457 straight_corners[RIGHT]));
463 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
465 extract_grob_set (me, "note-columns", columns);
467 while (l < columns.size () && Note_column::has_rests (columns[l]))
470 vsize r = columns.size ();
471 while (r > l && Note_column::has_rests (columns[r-1]))
479 *right = columns[r-1];
484 use first -> last note for slope, and then correct for disturbing
487 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
489 Spanner *me = dynamic_cast<Spanner *> (me_grob);
491 extract_grob_set (me, "note-columns", columns);
492 extract_grob_set (me, "tuplets", tuplets);
494 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
495 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
496 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
497 commony = st->common_refpoint (commony, Y_AXIS);
498 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
500 Grob *commonx = get_common_x (me);
501 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
504 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
506 /* staff-padding doesn't work correctly on cross-staff tuplets
507 because it only considers one staff symbol. Until this works,
509 if (st && !to_boolean (me->get_property ("cross-staff")))
511 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
514 staff = st->extent (commony, Y_AXIS) - my_offset;
519 Direction dir = get_grob_direction (me);
521 bool equally_long = false;
522 Grob *par_beam = parallel_beam (me, columns, &equally_long);
524 Item *lgr = get_x_bound_item (me, LEFT, dir);
525 Item *rgr = get_x_bound_item (me, RIGHT, dir);
526 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
527 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
528 bool follow_beam = par_beam
529 && get_grob_direction (par_beam) == dir
530 && ! to_boolean (par_beam->get_property ("knee"));
532 vector<Offset> points;
535 && Note_column::get_stem (columns[0])
536 && Note_column::get_stem (columns.back ()))
539 trigger set_stem_ends
541 (void) par_beam->get_property ("quantized-positions");
543 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
544 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);
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);
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 = Note_column::cross_staff_extent (right_col, commony);
567 Interval lv = Note_column::cross_staff_extent (left_col, commony);
571 Real graphical_dy = rv[dir] - lv[dir];
573 Slice ls = Note_column::head_positions_interval (left_col);
574 Slice rs = Note_column::head_positions_interval (right_col);
577 musical_dy[UP] = rs[UP] - ls[UP];
578 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
579 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
581 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
589 for (vsize i = 0; i < columns.size (); i++)
591 Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
592 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
594 points.push_back (Offset (x, note_ext[dir]));
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 + my_offset;
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);
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"
770 " beyond the staff. At a line break, the markups in the"
771 " @code{edge-text} are printed at the edges.",
775 "bracket-visibility "
777 "connect-to-neighbor "
782 "full-length-padding "