2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2011 Jan Nieuwenhuizen <janneke@gnu.org>
5 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 LilyPond is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 LilyPond is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
24 - tuplet bracket should probably be subject to the same rules as
25 beam sloping/quanting.
27 - There is no support for kneed brackets, or nested brackets.
29 - number placement for parallel beams should be much more advanced:
30 for sloped beams some extra horizontal offset must be introduced.
32 - number placement is usually done over the center note, not the
37 TODO: quantise, we don't want to collide with staff lines.
38 (or should we be above staff?)
40 todo: handle breaking elegantly.
43 #include "tuplet-bracket.hh"
44 #include "line-interface.hh"
47 #include "output-def.hh"
48 #include "font-interface.hh"
49 #include "text-interface.hh"
51 #include "note-column.hh"
52 #include "pointer-group-interface.hh"
53 #include "directional-element-interface.hh"
55 #include "staff-symbol-referencer.hh"
57 #include "paper-column.hh"
61 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
63 Spanner *me = dynamic_cast<Spanner *> (me_grob);
64 Item *g = me->get_bound (hdir);
65 if (Note_column::has_interface (g)
66 && Note_column::get_stem (g)
67 && Note_column::dir (g) == my_dir)
68 g = Note_column::get_stem (g);
74 flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
76 Drul_array<Real> zero (0, 0);
78 = robust_scm2drul (me->internal_get_property (sym), zero);
81 me->set_property (sym, ly_interval2scm (pair));
85 Return beam that encompasses the span of the tuplet bracket.
88 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
91 Spanner *me = dynamic_cast<Spanner *> (me_grob);
93 if (me->get_bound (LEFT)->break_status_dir ()
94 || me->get_bound (RIGHT)->break_status_dir ())
97 Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
98 Note_column::get_stem (cols.back ()));
102 || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
103 != me->get_bound (RIGHT)->get_column ()))
106 Drul_array<Grob *> beams;
109 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
110 while (flip (&d) != LEFT);
112 *equally_long = false;
113 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
116 extract_grob_set (beams[LEFT], "stems", beam_stems);
117 if (beam_stems.size () == 0)
119 programming_error ("beam under tuplet bracket has no stems");
125 = (beam_stems[0] == stems[LEFT]
126 && beam_stems.back () == stems[RIGHT]);
130 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
132 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
134 Spanner *me = unsmob_spanner (smob);
136 Direction dir = get_grob_direction (me);
137 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
138 get_x_bound_item (me, RIGHT, dir));
140 Drul_array<bool> connect_to_other (false, false);
144 Direction break_dir = bounds[d]->break_status_dir ();
145 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
146 vsize neighbor_idx = me->get_break_index () - break_dir;
149 && neighbor_idx < orig_spanner->broken_intos_.size ())
151 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
153 /* trigger possible suicide*/
154 (void) neighbor->get_property ("positions");
159 && neighbor_idx < orig_spanner->broken_intos_.size ()
160 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
162 while (flip (&d) != LEFT);
164 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
165 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
166 scm_from_bool (connect_to_other[RIGHT]));
172 Tuplet_bracket::get_common_x (Spanner *me)
174 extract_grob_set (me, "note-columns", columns);
176 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
177 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
178 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
183 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points, 1)
185 Tuplet_bracket::calc_control_points (SCM smob)
187 Spanner *me = unsmob_spanner (smob);
189 extract_grob_set (me, "note-columns", columns);
191 SCM scm_positions = me->get_property ("positions");
195 if (!scm_is_pair (scm_positions))
196 programming_error ("Positions should be number pair");
198 Drul_array<Real> positions
199 = robust_scm2drul (scm_positions, Drul_array<Real> (0, 0));
201 Grob *commonx = get_common_x (me);
202 Direction dir = get_grob_direction (me);
204 Drul_array<Item *> bounds;
205 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
206 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
208 Drul_array<bool> connect_to_other
209 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
210 Drul_array<bool> (false, false));
216 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
218 if (connect_to_other[d])
220 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
221 Interval (-0.5, 0.0)));
224 x_span[d] += d * overshoot[d];
226 x_span[d] = robust_relative_extent (bounds[d],
227 commonx, X_AXIS)[RIGHT]
233 || (bounds[d]->get_column ()
234 != dynamic_cast<Item *> (columns.back ())->get_column ())))
237 We're connecting to a column, for the last bit of a broken
241 = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
243 if (bounds[d]->break_status_dir ())
246 Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
247 if (to_boolean (me->get_property ("full-length-to-extent")))
248 coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
250 coord = max (coord, x_span[LEFT]);
252 x_span[d] = coord - padding;
255 while (flip (&d) != LEFT);
257 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
258 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
259 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
265 in the case that there is no bracket, but there is a (single) beam,
266 follow beam precisely for determining tuplet number location.
268 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
270 Tuplet_bracket::print (SCM smob)
272 Spanner *me = unsmob_spanner (smob);
275 extract_grob_set (me, "note-columns", columns);
276 bool equally_long = false;
277 Grob *par_beam = parallel_beam (me, columns, &equally_long);
279 bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
281 FIXME: The type of this prop is sucky.
283 SCM bracket_vis_prop = me->get_property ("bracket-visibility");
284 bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
285 bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
286 if (scm_is_bool (bracket_vis_prop))
287 bracket_visibility = bracket_prop;
289 bracket_visibility = !par_beam;
292 Don't print a tuplet bracket and number if
293 no control-points were calculated
295 SCM cpoints = me->get_property ("control-points");
296 if (scm_ilength (cpoints) < 2)
301 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
302 the bracket, but still let the number be displayed.
303 Only do this if the user has not explicitly specified bracket-visibility = #t.
305 if (!to_boolean (bracket_vis_prop)
306 && (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
307 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0))))
308 bracket_visibility = false;
310 Drul_array<Offset> points;
311 points[LEFT] = ly_scm2offset (scm_car (cpoints));
312 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
314 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
315 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
317 Output_def *pap = me->layout ();
319 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
322 Don't print the bracket when it would be smaller than the number.
323 ...Unless the user has coded bracket-visibility = #t, that is.
326 if (bracket_visibility && number_grob)
328 Interval ext = number_grob->extent (number_grob, X_AXIS);
329 if (!ext.is_empty ())
331 gap = ext.length () + 1.0;
333 if ((0.75 * x_span.length () < gap) && !bracket_prop)
334 bracket_visibility = false;
338 if (bracket_visibility)
340 Drul_array<Real> zero (0, 0);
341 Real ss = Staff_symbol_referencer::staff_space (me);
342 Drul_array<Real> height
343 = robust_scm2drul (me->get_property ("edge-height"), zero);
344 Drul_array<Real> flare
345 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
346 Drul_array<Real> shorten
347 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
348 Drul_array<Stencil> edge_stencils;
350 Direction dir = get_grob_direction (me);
352 scale_drul (&height, -ss * dir);
353 scale_drul (&flare, ss);
354 scale_drul (&shorten, ss);
356 Drul_array<bool> connect_to_other
357 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
358 Drul_array<bool> (false, false));
363 if (connect_to_other[d])
369 SCM edge_text = me->get_property ("edge-text");
371 if (scm_is_pair (edge_text))
373 SCM properties = Font_interface::text_font_alist_chain (me);
374 SCM text = index_get_cell (edge_text, d);
375 if (Text_interface::is_markup (text))
378 = Text_interface::interpret_markup (pap->self_scm (),
381 Stencil *edge_text = unsmob_stencil (t);
382 edge_text->translate_axis (x_span[d] - x_span[LEFT],
384 edge_stencils[d] = *edge_text;
389 while (flip (&d) != LEFT);
391 Stencil brack = make_bracket (me, Y_AXIS,
392 points[RIGHT] - points[LEFT],
395 0.1 = more space at right due to italics
396 TODO: use italic correction of font.
398 Interval (-0.5, 0.5) * gap + 0.1,
403 if (!edge_stencils[d].is_empty ())
404 brack.add_stencil (edge_stencils[d]);
406 while (flip (&d) != LEFT);
408 mol.add_stencil (brack);
411 mol.translate (points[LEFT]);
412 return mol.smobbed_copy ();
416 should move to lookup?
418 TODO: this will fail for very short (shorter than the flare)
422 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
423 Axis protrusion_axis,
425 Drul_array<Real> height,
427 Drul_array<Real> flare,
428 Drul_array<Real> shorten)
430 Drul_array<Offset> corners (Offset (0, 0), dz);
432 Real length = dz.length ();
433 Drul_array<Offset> gap_corners;
435 Axis bracket_axis = other_axis (protrusion_axis);
437 Drul_array<Offset> straight_corners = corners;
441 straight_corners[d] += -d * shorten[d] / length * dz;
442 while (flip (&d) != LEFT);
444 if (!gap.is_empty ())
447 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
448 while (flip (&d) != LEFT);
451 Drul_array<Offset> flare_corners = straight_corners;
454 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
455 flare_corners[d][protrusion_axis] += height[d];
456 straight_corners[d][bracket_axis] += -d * flare[d];
458 while (flip (&d) != LEFT);
463 if (!gap.is_empty ())
464 m.add_stencil (Line_interface::line (me, straight_corners[d],
467 m.add_stencil (Line_interface::line (me, straight_corners[d],
471 while (flip (&d) != LEFT);
474 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
475 straight_corners[RIGHT]));
481 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
483 extract_grob_set (me, "note-columns", columns);
485 while (l < columns.size () && Note_column::has_rests (columns[l]))
488 vsize r = columns.size ();
489 while (r > l && Note_column::has_rests (columns[r - 1]))
497 *right = columns[r - 1];
502 use first -> last note for slope, and then correct for disturbing
505 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
507 Spanner *me = dynamic_cast<Spanner *> (me_grob);
509 extract_grob_set (me, "note-columns", columns);
510 extract_grob_set (me, "tuplets", tuplets);
512 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
513 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
514 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
515 commony = st->common_refpoint (commony, Y_AXIS);
516 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
518 Grob *commonx = get_common_x (me);
519 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
522 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
524 /* staff-padding doesn't work correctly on cross-staff tuplets
525 because it only considers one staff symbol. Until this works,
527 if (st && !to_boolean (me->get_property ("cross-staff")))
529 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
532 staff = st->extent (commony, Y_AXIS) - my_offset;
537 Direction dir = get_grob_direction (me);
539 bool equally_long = false;
540 Grob *par_beam = parallel_beam (me, columns, &equally_long);
542 Item *lgr = get_x_bound_item (me, LEFT, dir);
543 Item *rgr = get_x_bound_item (me, RIGHT, dir);
544 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
545 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
546 bool follow_beam = par_beam
547 && get_grob_direction (par_beam) == dir
548 && !to_boolean (par_beam->get_property ("knee"));
550 vector<Offset> points;
553 && Note_column::get_stem (columns[0])
554 && Note_column::get_stem (columns.back ()))
556 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
557 Note_column::get_stem (columns.back ()));
559 Real lp = stems[LEFT]->extent (stems[LEFT], Y_AXIS)[get_grob_direction (stems[LEFT])]
560 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
561 Real rp = stems[RIGHT]->extent (stems[RIGHT], Y_AXIS)[get_grob_direction (stems[RIGHT])]
562 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
565 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
566 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
571 Use outer non-rest columns to determine slope
575 get_bounds (me, &left_col, &right_col);
576 if (left_col && right_col)
578 Interval rv = Note_column::cross_staff_extent (right_col, commony);
579 Interval lv = Note_column::cross_staff_extent (left_col, commony);
583 Real graphical_dy = rv[dir] - lv[dir];
585 Slice ls = Note_column::head_positions_interval (left_col);
586 Slice rs = Note_column::head_positions_interval (right_col);
589 musical_dy[UP] = rs[UP] - ls[UP];
590 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
591 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
593 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
601 for (vsize i = 0; i < columns.size (); i++)
603 Interval note_ext = Note_column::cross_staff_extent (columns[i],
605 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
607 points.push_back (Offset (x, note_ext[dir]));
613 points.push_back (Offset (x0 - x0, staff[dir]));
614 points.push_back (Offset (x1 - x0, staff[dir]));
618 This is a slight hack. We compute two encompass points from the
619 bbox of the smaller tuplets.
621 We assume that the smaller bracket is 1.0 space high.
623 Real ss = Staff_symbol_referencer::staff_space (me);
624 for (vsize i = 0; i < tuplets.size (); i++)
626 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
627 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
629 if (!tuplets[i]->is_live ())
633 Drul_array<Real> positions
634 = robust_scm2interval (tuplets[i]->get_property ("positions"),
637 Real other_dy = positions[RIGHT] - positions[LEFT];
642 = tuplet_y.linear_combination (d * sign (other_dy));
645 We don't take padding into account for nested tuplets.
646 the edges can come very close to the stems, likewise for
650 points.push_back (Offset (tuplet_x[d] - x0, y));
652 while (flip (&d) != LEFT);
655 *offset = -dir * infinity_f;
656 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
657 for (vsize i = 0; i < points.size (); i++)
659 Real x = points[i][X_AXIS];
660 Real tuplety = (*dy) * x * factor + my_offset;
662 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
663 *offset = points[i][Y_AXIS] - tuplety;
666 *offset += scm_to_double (me->get_property ("padding")) * dir;
669 horizontal brackets should not collide with staff lines.
671 Kind of pointless since we put them outside the staff anyway, but
672 let's leave code for the future when possibly allow them to move
673 into the staff once again.
675 This doesn't seem to support cross-staff tuplets atm.
678 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
680 // quantize, then do collision check.
683 *offset = rint (*offset);
684 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
691 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
693 Tuplet_bracket::calc_direction (SCM smob)
695 Grob *me = unsmob_grob (smob);
696 Direction dir = Tuplet_bracket::get_default_dir (me);
697 return scm_from_int (dir);
700 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
702 Tuplet_bracket::calc_positions (SCM smob)
704 Spanner *me = unsmob_spanner (smob);
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 if (Note_column::has_rests (nc))
729 Direction d = Note_column::dir (nc);
734 if (dirs[UP] == dirs[DOWN])
738 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
741 Interval staff_extent = staff->extent (staff, Y_AXIS);
742 Interval extremal_positions;
743 extremal_positions.set_empty ();
744 for (vsize i = 0; i < columns.size (); i++)
746 Direction d = Note_column::dir (columns[i]);
747 extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
751 extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
752 while (flip (&d) != LEFT);
754 return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
757 return dirs[UP] > dirs[DOWN] ? UP : DOWN;
761 Tuplet_bracket::add_column (Grob *me, Item *n)
763 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
764 add_bound_item (dynamic_cast<Spanner *> (me), n);
768 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
770 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
773 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
775 Tuplet_bracket::calc_cross_staff (SCM smob)
777 Grob *me = unsmob_grob (smob);
778 extract_grob_set (me, "note-columns", cols);
779 extract_grob_set (me, "tuplets", tuplets);
781 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
782 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
783 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
784 commony = st->common_refpoint (commony, Y_AXIS);
785 if (me->check_cross_staff (commony))
788 bool equally_long = false;
789 Grob *par_beam = parallel_beam (me, cols, &equally_long);
791 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
794 for (vsize i = 0; i < cols.size (); i++)
796 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
797 if (stem && to_boolean (stem->get_property ("cross-staff")))
804 ADD_INTERFACE (Tuplet_bracket,
805 "A bracket with a number in the middle, used for tuplets."
806 " When the bracket spans a line break, the value of"
807 " @code{break-overshoot} determines how far it extends"
808 " beyond the staff. At a line break, the markups in the"
809 " @code{edge-text} are printed at the edges.",
813 "bracket-visibility "
815 "connect-to-neighbor "
820 "full-length-padding "
821 "full-length-to-extent "