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 "axis-group-interface.hh"
45 #include "line-interface.hh"
48 #include "output-def.hh"
49 #include "font-interface.hh"
50 #include "text-interface.hh"
52 #include "note-column.hh"
53 #include "pointer-group-interface.hh"
54 #include "directional-element-interface.hh"
57 #include "staff-symbol-referencer.hh"
59 #include "paper-column.hh"
63 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
65 Spanner *me = dynamic_cast<Spanner *> (me_grob);
66 Item *g = me->get_bound (hdir);
67 if (Note_column::has_interface (g)
68 && Note_column::get_stem (g)
69 && Note_column::dir (g) == my_dir)
70 g = Note_column::get_stem (g);
76 flatten_number_pair_property (Grob *me, Direction xdir, SCM sym)
78 Drul_array<Real> zero (0, 0);
80 = robust_scm2drul (me->internal_get_property (sym), zero);
83 me->set_property (sym, ly_interval2scm (pair));
87 Return beam that encompasses the span of the tuplet bracket.
90 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
93 Spanner *me = dynamic_cast<Spanner *> (me_grob);
95 if (me->get_bound (LEFT)->break_status_dir ()
96 || me->get_bound (RIGHT)->break_status_dir ())
99 Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
100 Note_column::get_stem (cols.back ()));
104 || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
105 != me->get_bound (RIGHT)->get_column ()))
108 Drul_array<Grob *> beams;
111 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
112 while (flip (&d) != LEFT);
114 *equally_long = false;
115 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
118 extract_grob_set (beams[LEFT], "stems", beam_stems);
119 if (beam_stems.size () == 0)
121 programming_error ("beam under tuplet bracket has no stems");
127 = (beam_stems[0] == stems[LEFT]
128 && beam_stems.back () == stems[RIGHT]);
132 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
134 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
136 Spanner *me = unsmob_spanner (smob);
138 Direction dir = get_grob_direction (me);
139 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
140 get_x_bound_item (me, RIGHT, dir));
142 Drul_array<bool> connect_to_other (false, false);
146 Direction break_dir = bounds[d]->break_status_dir ();
147 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
148 vsize neighbor_idx = me->get_break_index () - break_dir;
151 && neighbor_idx < orig_spanner->broken_intos_.size ())
153 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
155 /* trigger possible suicide*/
156 (void) neighbor->get_property ("positions");
161 && neighbor_idx < orig_spanner->broken_intos_.size ()
162 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
164 while (flip (&d) != LEFT);
166 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
167 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
168 scm_from_bool (connect_to_other[RIGHT]));
174 Tuplet_bracket::get_common_x (Spanner *me)
176 extract_grob_set (me, "note-columns", columns);
178 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
179 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
180 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
185 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_x_positions, 1)
187 Tuplet_bracket::calc_x_positions (SCM smob)
189 Spanner *me = unsmob_spanner (smob);
190 extract_grob_set (me, "note-columns", columns);
192 Grob *commonx = get_common_x (me);
193 Direction dir = get_grob_direction (me);
195 Drul_array<Item *> bounds;
196 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
197 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
199 Drul_array<bool> connect_to_other
200 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
201 Drul_array<bool> (false, false));
207 x_span[d] = bounds[d]->break_status_dir ()
208 ? Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[d]
209 : robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
210 if (connect_to_other[d])
212 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
213 Interval (-0.5, 0.0)));
216 x_span[d] += d * overshoot[d];
218 x_span[d] = (bounds[d]->break_status_dir ()
219 ? Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d]
220 : robust_relative_extent (bounds[d], commonx, X_AXIS)[-d])
226 || (bounds[d]->get_column ()
227 != dynamic_cast<Item *> (columns.back ())->get_column ())))
230 We're connecting to a column, for the last bit of a broken
234 = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
236 if (bounds[d]->break_status_dir ())
239 Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
240 if (to_boolean (me->get_property ("full-length-to-extent")))
241 coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
243 coord = max (coord, x_span[LEFT]);
245 x_span[d] = coord - padding;
248 while (flip (&d) != LEFT);
250 return ly_interval2scm (x_span - me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS));
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); // Flag, print/don't print tuplet bracket.
272 FIXME: The type of this prop is sucky.
274 SCM bracket_vis_prop = me->get_property ("bracket-visibility");
275 bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
276 bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
277 if (scm_is_bool (bracket_vis_prop))
278 bracket_visibility = bracket_prop;
280 bracket_visibility = !par_beam;
283 Don't print a tuplet bracket and number if
284 no X or Y positions were calculated.
286 SCM scm_x_span = me->get_property ("X-positions");
287 SCM scm_positions = me->get_property ("positions");
288 if (!scm_is_pair (scm_x_span) || !scm_is_pair (scm_positions))
293 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
294 the bracket, but still let the number be displayed.
295 Only do this if the user has not explicitly specified bracket-visibility = #t.
297 if (!to_boolean (bracket_vis_prop)
298 && (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
299 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0))))
300 bracket_visibility = false;
302 Interval x_span = robust_scm2interval (scm_x_span, Interval (0.0, 0.0));
303 Interval positions = robust_scm2interval (scm_positions, Interval (0.0, 0.0));
305 Drul_array<Offset> points;
308 points[d] = Offset (x_span[d], positions[d]);
309 while (flip (&d) != LEFT);
311 Output_def *pap = me->layout ();
313 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
316 Don't print the bracket when it would be smaller than the number.
317 ...Unless the user has coded bracket-visibility = #t, that is.
320 if (bracket_visibility && number_grob)
322 Interval ext = number_grob->extent (number_grob, X_AXIS);
323 if (!ext.is_empty ())
325 gap = ext.length () + 1.0;
327 if ((0.75 * x_span.length () < gap) && !bracket_prop)
328 bracket_visibility = false;
332 if (bracket_visibility)
334 Drul_array<Real> zero (0, 0);
335 Real ss = Staff_symbol_referencer::staff_space (me);
336 Drul_array<Real> height
337 = robust_scm2drul (me->get_property ("edge-height"), zero);
338 Drul_array<Real> flare
339 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
340 Drul_array<Real> shorten
341 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
342 Drul_array<Stencil> edge_stencils;
344 Direction dir = get_grob_direction (me);
346 scale_drul (&height, -ss * dir);
347 scale_drul (&flare, ss);
348 scale_drul (&shorten, ss);
350 Drul_array<bool> connect_to_other
351 = robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
352 Drul_array<bool> (false, false));
357 if (connect_to_other[d])
363 SCM edge_text = me->get_property ("edge-text");
365 if (scm_is_pair (edge_text))
367 SCM properties = Font_interface::text_font_alist_chain (me);
368 SCM text = index_get_cell (edge_text, d);
369 if (Text_interface::is_markup (text))
372 = Text_interface::interpret_markup (pap->self_scm (),
375 Stencil *edge_text = unsmob_stencil (t);
376 edge_text->translate_axis (x_span[d] - x_span[LEFT],
378 edge_stencils[d] = *edge_text;
383 while (flip (&d) != LEFT);
385 Stencil brack = make_bracket (me, Y_AXIS,
386 points[RIGHT] - points[LEFT],
389 0.1 = more space at right due to italics
390 TODO: use italic correction of font.
392 Interval (-0.5, 0.5) * gap + 0.1,
397 if (!edge_stencils[d].is_empty ())
398 brack.add_stencil (edge_stencils[d]);
400 while (flip (&d) != LEFT);
402 mol.add_stencil (brack);
405 mol.translate (points[LEFT]);
406 return mol.smobbed_copy ();
410 should move to lookup?
412 TODO: this will fail for very short (shorter than the flare)
416 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
417 Axis protrusion_axis,
419 Drul_array<Real> height,
421 Drul_array<Real> flare,
422 Drul_array<Real> shorten)
424 Drul_array<Offset> corners (Offset (0, 0), dz);
426 Real length = dz.length ();
427 Drul_array<Offset> gap_corners;
429 Axis bracket_axis = other_axis (protrusion_axis);
431 Drul_array<Offset> straight_corners = corners;
435 straight_corners[d] += -d * shorten[d] / length * dz;
436 while (flip (&d) != LEFT);
438 if (!gap.is_empty ())
441 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
442 while (flip (&d) != LEFT);
445 Drul_array<Offset> flare_corners = straight_corners;
448 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
449 flare_corners[d][protrusion_axis] += height[d];
450 straight_corners[d][bracket_axis] += -d * flare[d];
452 while (flip (&d) != LEFT);
457 if (!gap.is_empty ())
458 m.add_stencil (Line_interface::line (me, straight_corners[d],
461 m.add_stencil (Line_interface::line (me, straight_corners[d],
465 while (flip (&d) != LEFT);
468 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
469 straight_corners[RIGHT]));
475 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
477 extract_grob_set (me, "note-columns", columns);
479 while (l < columns.size () && Note_column::has_rests (columns[l]))
482 vsize r = columns.size ();
483 while (r > l && Note_column::has_rests (columns[r - 1]))
491 *right = columns[r - 1];
496 use first -> last note for slope, and then correct for disturbing
499 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
501 Spanner *me = dynamic_cast<Spanner *> (me_grob);
503 extract_grob_set (me, "note-columns", columns);
504 extract_grob_set (me, "tuplets", tuplets);
506 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
507 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
508 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
509 commony = st->common_refpoint (commony, Y_AXIS);
510 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
512 Grob *commonx = get_common_x (me);
513 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
516 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
518 /* staff-padding doesn't work correctly on cross-staff tuplets
519 because it only considers one staff symbol. Until this works,
521 if (st && !to_boolean (me->get_property ("cross-staff")))
523 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
526 staff = st->extent (commony, Y_AXIS) - my_offset;
531 Direction dir = get_grob_direction (me);
533 bool equally_long = false;
534 Grob *par_beam = parallel_beam (me, columns, &equally_long);
536 Item *lgr = get_x_bound_item (me, LEFT, dir);
537 Item *rgr = get_x_bound_item (me, RIGHT, dir);
538 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
539 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
540 bool follow_beam = par_beam
541 && get_grob_direction (par_beam) == dir
542 && !to_boolean (par_beam->get_property ("knee"));
544 vector<Offset> points;
547 && Note_column::get_stem (columns[0])
548 && Note_column::get_stem (columns.back ()))
550 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
551 Note_column::get_stem (columns.back ()));
554 Direction side = LEFT;
557 // Trigger setting of stem lengths if necessary.
558 if (Grob *beam = Stem::get_beam (stems[side]))
559 (void) beam->get_property ("quantized-positions");
560 poss[side] = stems[side]->extent (stems[side], Y_AXIS)[get_grob_direction (stems[side])]
561 + stems[side]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
563 while (flip (&side) != LEFT);
565 *dy = poss[RIGHT] - poss[LEFT];
566 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, poss[LEFT]));
567 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, poss[RIGHT]));
572 Use outer non-rest columns to determine slope
576 get_bounds (me, &left_col, &right_col);
577 if (left_col && right_col)
579 Interval rv = Note_column::cross_staff_extent (right_col, commony);
580 Interval lv = Note_column::cross_staff_extent (left_col, commony);
584 Real graphical_dy = rv[dir] - lv[dir];
586 Slice ls = Note_column::head_positions_interval (left_col);
587 Slice rs = Note_column::head_positions_interval (right_col);
590 musical_dy[UP] = rs[UP] - ls[UP];
591 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
592 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
594 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
602 for (vsize i = 0; i < columns.size (); i++)
604 Interval note_ext = Note_column::cross_staff_extent (columns[i],
606 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
608 points.push_back (Offset (x, note_ext[dir]));
614 points.push_back (Offset (x0 - x0, staff[dir]));
615 points.push_back (Offset (x1 - x0, staff[dir]));
619 This is a slight hack. We compute two encompass points from the
620 bbox of the smaller tuplets.
622 We assume that the smaller bracket is 1.0 space high.
624 Real ss = Staff_symbol_referencer::staff_space (me);
625 for (vsize i = 0; i < tuplets.size (); i++)
627 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
628 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
630 if (!tuplets[i]->is_live ())
634 Drul_array<Real> positions
635 = robust_scm2interval (tuplets[i]->get_property ("positions"),
638 Real other_dy = positions[RIGHT] - positions[LEFT];
643 = tuplet_y.linear_combination (d * sign (other_dy));
646 We don't take padding into account for nested tuplets.
647 the edges can come very close to the stems, likewise for
651 points.push_back (Offset (tuplet_x[d] - x0, y));
653 while (flip (&d) != LEFT);
654 // Check for number-on-bracket collisions
655 Grob *number = unsmob_grob (tuplets[i]->get_object ("tuplet-number"));
657 points.push_back (Offset (number->extent (commonx, X_AXIS).center () - x0,
658 number->extent (commony, Y_AXIS)[dir]));
661 if (to_boolean (me->get_property ("avoid-scripts"))
662 && !scm_is_number (me->get_property ("outside-staff-priority")))
664 extract_grob_set (me, "scripts", scripts);
665 for (vsize i = 0; i < scripts.size (); i++)
667 if (!scripts[i]->is_live ())
669 if (scm_is_number (scripts[i]->get_property ("outside-staff-priority")))
672 Interval script_x (scripts[i]->extent (commonx, X_AXIS));
673 Interval script_y (scripts[i]->extent (commony, Y_AXIS));
675 points.push_back (Offset (script_x.center () - x0,
680 *offset = -dir * infinity_f;
681 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
682 for (vsize i = 0; i < points.size (); i++)
684 Real x = points[i][X_AXIS];
685 Real tuplety = (*dy) * x * factor + my_offset;
687 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
688 *offset = points[i][Y_AXIS] - tuplety;
691 *offset += scm_to_double (me->get_property ("padding")) * dir;
694 horizontal brackets should not collide with staff lines.
696 Kind of pointless since we put them outside the staff anyway, but
697 let's leave code for the future when possibly allow them to move
698 into the staff once again.
700 This doesn't seem to support cross-staff tuplets atm.
703 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
705 // quantize, then do collision check.
708 *offset = rint (*offset);
709 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
716 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
718 Tuplet_bracket::calc_direction (SCM smob)
720 Grob *me = unsmob_grob (smob);
721 Direction dir = Tuplet_bracket::get_default_dir (me);
722 return scm_from_int (dir);
725 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
727 Tuplet_bracket::calc_positions (SCM smob)
729 Spanner *me = unsmob_spanner (smob);
733 calc_position_and_height (me, &offset, &dy);
735 SCM x = scm_cons (scm_from_double (offset),
736 scm_from_double (offset + dy));
745 Tuplet_bracket::get_default_dir (Grob *me)
747 Drul_array<int> dirs (0, 0);
748 extract_grob_set (me, "note-columns", columns);
749 for (vsize i = 0; i < columns.size (); i++)
751 Grob *nc = columns[i];
752 if (Note_column::has_rests (nc))
754 Direction d = Note_column::dir (nc);
759 if (dirs[UP] == dirs[DOWN])
763 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
766 Interval staff_extent = staff->extent (staff, Y_AXIS);
767 Interval extremal_positions;
768 extremal_positions.set_empty ();
769 for (vsize i = 0; i < columns.size (); i++)
771 Direction d = Note_column::dir (columns[i]);
772 extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
776 extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
777 while (flip (&d) != LEFT);
779 return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
782 return dirs[UP] > dirs[DOWN] ? UP : DOWN;
786 Tuplet_bracket::add_column (Grob *me, Item *n)
788 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
789 add_bound_item (dynamic_cast<Spanner *> (me), n);
793 Tuplet_bracket::add_script (Grob *me, Item *s)
795 Pointer_group_interface::add_grob (me, ly_symbol2scm ("scripts"), s);
799 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
801 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
804 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
806 Tuplet_bracket::calc_cross_staff (SCM smob)
808 Grob *me = unsmob_grob (smob);
809 extract_grob_set (me, "note-columns", cols);
810 extract_grob_set (me, "tuplets", tuplets);
812 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
813 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
814 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
815 commony = st->common_refpoint (commony, Y_AXIS);
816 if (me->check_cross_staff (commony))
819 bool equally_long = false;
820 Grob *par_beam = parallel_beam (me, cols, &equally_long);
822 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
825 for (vsize i = 0; i < cols.size (); i++)
827 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
828 if (stem && to_boolean (stem->get_property ("cross-staff")))
835 ADD_INTERFACE (Tuplet_bracket,
836 "A bracket with a number in the middle, used for tuplets."
837 " When the bracket spans a line break, the value of"
838 " @code{break-overshoot} determines how far it extends"
839 " beyond the staff. At a line break, the markups in the"
840 " @code{edge-text} are printed at the edges.",
845 "bracket-visibility "
847 "connect-to-neighbor "
851 "full-length-padding "
852 "full-length-to-extent "