2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2012 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"
56 #include "staff-symbol-referencer.hh"
58 #include "paper-column.hh"
62 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
64 Spanner *me = dynamic_cast<Spanner *> (me_grob);
65 Item *g = me->get_bound (hdir);
66 if (Note_column::has_interface (g)
67 && Note_column::get_stem (g)
68 && Note_column::dir (g) == my_dir)
69 g = Note_column::get_stem (g);
75 flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
77 Drul_array<Real> zero (0, 0);
79 = robust_scm2drul (me->internal_get_property (sym), zero);
82 me->set_property (sym, ly_interval2scm (pair));
86 Return beam that encompasses the span of the tuplet bracket.
89 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
92 Spanner *me = dynamic_cast<Spanner *> (me_grob);
94 if (me->get_bound (LEFT)->break_status_dir ()
95 || me->get_bound (RIGHT)->break_status_dir ())
98 Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
99 Note_column::get_stem (cols.back ()));
103 || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
104 != me->get_bound (RIGHT)->get_column ()))
107 Drul_array<Grob *> beams;
110 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
111 while (flip (&d) != LEFT);
113 *equally_long = false;
114 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
117 extract_grob_set (beams[LEFT], "stems", beam_stems);
118 if (beam_stems.size () == 0)
120 programming_error ("beam under tuplet bracket has no stems");
126 = (beam_stems[0] == stems[LEFT]
127 && beam_stems.back () == stems[RIGHT]);
131 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
133 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
135 Spanner *me = unsmob_spanner (smob);
137 Direction dir = get_grob_direction (me);
138 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
139 get_x_bound_item (me, RIGHT, dir));
141 Drul_array<bool> connect_to_other (false, false);
145 Direction break_dir = bounds[d]->break_status_dir ();
146 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
147 vsize neighbor_idx = me->get_break_index () - break_dir;
150 && neighbor_idx < orig_spanner->broken_intos_.size ())
152 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
154 /* trigger possible suicide*/
155 (void) neighbor->get_property ("positions");
160 && neighbor_idx < orig_spanner->broken_intos_.size ()
161 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
163 while (flip (&d) != LEFT);
165 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
166 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
167 scm_from_bool (connect_to_other[RIGHT]));
173 Tuplet_bracket::get_common_x (Spanner *me)
175 extract_grob_set (me, "note-columns", columns);
177 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
178 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
179 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
184 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_x_positions, 1)
186 Tuplet_bracket::calc_x_positions (SCM smob)
188 Spanner *me = unsmob_spanner (smob);
189 extract_grob_set (me, "note-columns", columns);
191 Grob *commonx = get_common_x (me);
192 Direction dir = get_grob_direction (me);
194 Drul_array<Item *> bounds;
195 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
196 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
198 Drul_array<bool> connect_to_other
199 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
200 Drul_array<bool> (false, false));
206 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
208 if (connect_to_other[d])
210 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
211 Interval (-0.5, 0.0)));
214 x_span[d] += d * overshoot[d];
216 x_span[d] = robust_relative_extent (bounds[d],
217 commonx, X_AXIS)[RIGHT]
223 || (bounds[d]->get_column ()
224 != dynamic_cast<Item *> (columns.back ())->get_column ())))
227 We're connecting to a column, for the last bit of a broken
231 = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
233 if (bounds[d]->break_status_dir ())
236 Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
237 if (to_boolean (me->get_property ("full-length-to-extent")))
238 coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
240 coord = max (coord, x_span[LEFT]);
242 x_span[d] = coord - padding;
245 while (flip (&d) != LEFT);
247 return ly_interval2scm (x_span - me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS));
253 in the case that there is no bracket, but there is a (single) beam,
254 follow beam precisely for determining tuplet number location.
256 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
258 Tuplet_bracket::print (SCM smob)
260 Spanner *me = unsmob_spanner (smob);
263 extract_grob_set (me, "note-columns", columns);
264 bool equally_long = false;
265 Grob *par_beam = parallel_beam (me, columns, &equally_long);
267 bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
269 FIXME: The type of this prop is sucky.
271 SCM bracket_vis_prop = me->get_property ("bracket-visibility");
272 bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
273 bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
274 if (scm_is_bool (bracket_vis_prop))
275 bracket_visibility = bracket_prop;
277 bracket_visibility = !par_beam;
280 Don't print a tuplet bracket and number if
281 no X or Y positions were calculated.
283 SCM scm_x_span = me->get_property ("X-positions");
284 SCM scm_positions = me->get_property ("positions");
285 if (!scm_is_pair (scm_x_span) || !scm_is_pair (scm_positions))
290 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
291 the bracket, but still let the number be displayed.
292 Only do this if the user has not explicitly specified bracket-visibility = #t.
294 if (!to_boolean (bracket_vis_prop)
295 && (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
296 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0))))
297 bracket_visibility = false;
299 Interval x_span = robust_scm2interval (scm_x_span, Interval (0.0, 0.0));
300 Interval positions = robust_scm2interval (scm_positions, Interval (0.0, 0.0));
302 Drul_array<Offset> points;
305 points[d] = Offset (x_span[d], positions[d]);
306 while (flip (&d) != LEFT);
308 Output_def *pap = me->layout ();
310 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
313 Don't print the bracket when it would be smaller than the number.
314 ...Unless the user has coded bracket-visibility = #t, that is.
317 if (bracket_visibility && number_grob)
319 Interval ext = number_grob->extent (number_grob, X_AXIS);
320 if (!ext.is_empty ())
322 gap = ext.length () + 1.0;
324 if ((0.75 * x_span.length () < gap) && !bracket_prop)
325 bracket_visibility = false;
329 if (bracket_visibility)
331 Drul_array<Real> zero (0, 0);
332 Real ss = Staff_symbol_referencer::staff_space (me);
333 Drul_array<Real> height
334 = robust_scm2drul (me->get_property ("edge-height"), zero);
335 Drul_array<Real> flare
336 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
337 Drul_array<Real> shorten
338 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
339 Drul_array<Stencil> edge_stencils;
341 Direction dir = get_grob_direction (me);
343 scale_drul (&height, -ss * dir);
344 scale_drul (&flare, ss);
345 scale_drul (&shorten, ss);
347 Drul_array<bool> connect_to_other
348 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
349 Drul_array<bool> (false, false));
354 if (connect_to_other[d])
360 SCM edge_text = me->get_property ("edge-text");
362 if (scm_is_pair (edge_text))
364 SCM properties = Font_interface::text_font_alist_chain (me);
365 SCM text = index_get_cell (edge_text, d);
366 if (Text_interface::is_markup (text))
369 = Text_interface::interpret_markup (pap->self_scm (),
372 Stencil *edge_text = unsmob_stencil (t);
373 edge_text->translate_axis (x_span[d] - x_span[LEFT],
375 edge_stencils[d] = *edge_text;
380 while (flip (&d) != LEFT);
382 Stencil brack = make_bracket (me, Y_AXIS,
383 points[RIGHT] - points[LEFT],
386 0.1 = more space at right due to italics
387 TODO: use italic correction of font.
389 Interval (-0.5, 0.5) * gap + 0.1,
394 if (!edge_stencils[d].is_empty ())
395 brack.add_stencil (edge_stencils[d]);
397 while (flip (&d) != LEFT);
399 mol.add_stencil (brack);
402 mol.translate (points[LEFT]);
403 return mol.smobbed_copy ();
407 should move to lookup?
409 TODO: this will fail for very short (shorter than the flare)
413 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
414 Axis protrusion_axis,
416 Drul_array<Real> height,
418 Drul_array<Real> flare,
419 Drul_array<Real> shorten)
421 Drul_array<Offset> corners (Offset (0, 0), dz);
423 Real length = dz.length ();
424 Drul_array<Offset> gap_corners;
426 Axis bracket_axis = other_axis (protrusion_axis);
428 Drul_array<Offset> straight_corners = corners;
432 straight_corners[d] += -d * shorten[d] / length * dz;
433 while (flip (&d) != LEFT);
435 if (!gap.is_empty ())
438 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
439 while (flip (&d) != LEFT);
442 Drul_array<Offset> flare_corners = straight_corners;
445 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
446 flare_corners[d][protrusion_axis] += height[d];
447 straight_corners[d][bracket_axis] += -d * flare[d];
449 while (flip (&d) != LEFT);
454 if (!gap.is_empty ())
455 m.add_stencil (Line_interface::line (me, straight_corners[d],
458 m.add_stencil (Line_interface::line (me, straight_corners[d],
462 while (flip (&d) != LEFT);
465 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
466 straight_corners[RIGHT]));
472 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
474 extract_grob_set (me, "note-columns", columns);
476 while (l < columns.size () && Note_column::has_rests (columns[l]))
479 vsize r = columns.size ();
480 while (r > l && Note_column::has_rests (columns[r - 1]))
488 *right = columns[r - 1];
493 use first -> last note for slope, and then correct for disturbing
496 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
498 Spanner *me = dynamic_cast<Spanner *> (me_grob);
500 extract_grob_set (me, "note-columns", columns);
501 extract_grob_set (me, "tuplets", tuplets);
503 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
504 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
505 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
506 commony = st->common_refpoint (commony, Y_AXIS);
507 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
509 Grob *commonx = get_common_x (me);
510 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
513 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
515 /* staff-padding doesn't work correctly on cross-staff tuplets
516 because it only considers one staff symbol. Until this works,
518 if (st && !to_boolean (me->get_property ("cross-staff")))
520 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
523 staff = st->extent (commony, Y_AXIS) - my_offset;
528 Direction dir = get_grob_direction (me);
530 bool equally_long = false;
531 Grob *par_beam = parallel_beam (me, columns, &equally_long);
533 Item *lgr = get_x_bound_item (me, LEFT, dir);
534 Item *rgr = get_x_bound_item (me, RIGHT, dir);
535 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
536 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
537 bool follow_beam = par_beam
538 && get_grob_direction (par_beam) == dir
539 && !to_boolean (par_beam->get_property ("knee"));
541 vector<Offset> points;
544 && Note_column::get_stem (columns[0])
545 && Note_column::get_stem (columns.back ()))
547 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
548 Note_column::get_stem (columns.back ()));
551 Direction side = LEFT;
554 // Trigger setting of stem lengths if necessary.
555 if (Grob *beam = Stem::get_beam (stems[side]))
556 (void) beam->get_property ("quantized-positions");
557 poss[side] = stems[side]->extent (stems[side], Y_AXIS)[get_grob_direction (stems[side])]
558 + stems[side]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
560 while (flip (&side) != LEFT);
562 *dy = poss[RIGHT] - poss[LEFT];
563 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, poss[LEFT]));
564 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, poss[RIGHT]));
569 Use outer non-rest columns to determine slope
573 get_bounds (me, &left_col, &right_col);
574 if (left_col && right_col)
576 Interval rv = Note_column::cross_staff_extent (right_col, commony);
577 Interval lv = Note_column::cross_staff_extent (left_col, commony);
581 Real graphical_dy = rv[dir] - lv[dir];
583 Slice ls = Note_column::head_positions_interval (left_col);
584 Slice rs = Note_column::head_positions_interval (right_col);
587 musical_dy[UP] = rs[UP] - ls[UP];
588 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
589 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
591 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
599 for (vsize i = 0; i < columns.size (); i++)
601 Interval note_ext = Note_column::cross_staff_extent (columns[i],
603 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
605 points.push_back (Offset (x, note_ext[dir]));
611 points.push_back (Offset (x0 - x0, staff[dir]));
612 points.push_back (Offset (x1 - x0, staff[dir]));
616 This is a slight hack. We compute two encompass points from the
617 bbox of the smaller tuplets.
619 We assume that the smaller bracket is 1.0 space high.
621 Real ss = Staff_symbol_referencer::staff_space (me);
622 for (vsize i = 0; i < tuplets.size (); i++)
624 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
625 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
627 if (!tuplets[i]->is_live ())
631 Drul_array<Real> positions
632 = robust_scm2interval (tuplets[i]->get_property ("positions"),
635 Real other_dy = positions[RIGHT] - positions[LEFT];
640 = tuplet_y.linear_combination (d * sign (other_dy));
643 We don't take padding into account for nested tuplets.
644 the edges can come very close to the stems, likewise for
648 points.push_back (Offset (tuplet_x[d] - x0, y));
650 while (flip (&d) != LEFT);
651 // Check for number-on-bracket collisions
652 Grob *number = unsmob_grob (tuplets[i]->get_object ("tuplet-number"));
654 points.push_back (Offset (number->extent (commonx, X_AXIS).center () - x0,
655 number->extent (commony, Y_AXIS)[dir]));
658 if (to_boolean (me->get_property ("avoid-scripts")))
660 extract_grob_set (me, "scripts", scripts);
661 for (vsize i = 0; i < scripts.size (); i++)
663 if (!scripts[i]->is_live ())
666 Interval script_x (scripts[i]->extent (commonx, X_AXIS));
667 Interval script_y (scripts[i]->extent (commony, Y_AXIS));
669 points.push_back (Offset (script_x.center () - x0,
674 *offset = -dir * infinity_f;
675 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
676 for (vsize i = 0; i < points.size (); i++)
678 Real x = points[i][X_AXIS];
679 Real tuplety = (*dy) * x * factor + my_offset;
681 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
682 *offset = points[i][Y_AXIS] - tuplety;
685 *offset += scm_to_double (me->get_property ("padding")) * dir;
688 horizontal brackets should not collide with staff lines.
690 Kind of pointless since we put them outside the staff anyway, but
691 let's leave code for the future when possibly allow them to move
692 into the staff once again.
694 This doesn't seem to support cross-staff tuplets atm.
697 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
699 // quantize, then do collision check.
702 *offset = rint (*offset);
703 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
710 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
712 Tuplet_bracket::calc_direction (SCM smob)
714 Grob *me = unsmob_grob (smob);
715 Direction dir = Tuplet_bracket::get_default_dir (me);
716 return scm_from_int (dir);
719 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
721 Tuplet_bracket::calc_positions (SCM smob)
723 Spanner *me = unsmob_spanner (smob);
727 calc_position_and_height (me, &offset, &dy);
729 SCM x = scm_cons (scm_from_double (offset),
730 scm_from_double (offset + dy));
739 Tuplet_bracket::get_default_dir (Grob *me)
741 Drul_array<int> dirs (0, 0);
742 extract_grob_set (me, "note-columns", columns);
743 for (vsize i = 0; i < columns.size (); i++)
745 Grob *nc = columns[i];
746 if (Note_column::has_rests (nc))
748 Direction d = Note_column::dir (nc);
753 if (dirs[UP] == dirs[DOWN])
757 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
760 Interval staff_extent = staff->extent (staff, Y_AXIS);
761 Interval extremal_positions;
762 extremal_positions.set_empty ();
763 for (vsize i = 0; i < columns.size (); i++)
765 Direction d = Note_column::dir (columns[i]);
766 extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
770 extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
771 while (flip (&d) != LEFT);
773 return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
776 return dirs[UP] > dirs[DOWN] ? UP : DOWN;
780 Tuplet_bracket::add_column (Grob *me, Item *n)
782 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
783 add_bound_item (dynamic_cast<Spanner *> (me), n);
787 Tuplet_bracket::add_script (Grob *me, Item *s)
789 Pointer_group_interface::add_grob (me, ly_symbol2scm ("scripts"), s);
793 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
795 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
798 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
800 Tuplet_bracket::calc_cross_staff (SCM smob)
802 Grob *me = unsmob_grob (smob);
803 extract_grob_set (me, "note-columns", cols);
804 extract_grob_set (me, "tuplets", tuplets);
806 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
807 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
808 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
809 commony = st->common_refpoint (commony, Y_AXIS);
810 if (me->check_cross_staff (commony))
813 bool equally_long = false;
814 Grob *par_beam = parallel_beam (me, cols, &equally_long);
816 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
819 for (vsize i = 0; i < cols.size (); i++)
821 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
822 if (stem && to_boolean (stem->get_property ("cross-staff")))
829 ADD_INTERFACE (Tuplet_bracket,
830 "A bracket with a number in the middle, used for tuplets."
831 " When the bracket spans a line break, the value of"
832 " @code{break-overshoot} determines how far it extends"
833 " beyond the staff. At a line break, the markups in the"
834 " @code{edge-text} are printed at the edges.",
839 "bracket-visibility "
841 "connect-to-neighbor "
845 "full-length-padding "
846 "full-length-to-extent "