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"))
659 && !scm_is_number (me->get_property ("outside-staff-priority")))
661 extract_grob_set (me, "scripts", scripts);
662 for (vsize i = 0; i < scripts.size (); i++)
664 if (!scripts[i]->is_live ())
666 if (scm_is_number (scripts[i]->get_property ("outside-staff-priority")))
669 Interval script_x (scripts[i]->extent (commonx, X_AXIS));
670 Interval script_y (scripts[i]->extent (commony, Y_AXIS));
672 points.push_back (Offset (script_x.center () - x0,
677 *offset = -dir * infinity_f;
678 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
679 for (vsize i = 0; i < points.size (); i++)
681 Real x = points[i][X_AXIS];
682 Real tuplety = (*dy) * x * factor + my_offset;
684 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
685 *offset = points[i][Y_AXIS] - tuplety;
688 *offset += scm_to_double (me->get_property ("padding")) * dir;
691 horizontal brackets should not collide with staff lines.
693 Kind of pointless since we put them outside the staff anyway, but
694 let's leave code for the future when possibly allow them to move
695 into the staff once again.
697 This doesn't seem to support cross-staff tuplets atm.
700 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
702 // quantize, then do collision check.
705 *offset = rint (*offset);
706 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
713 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
715 Tuplet_bracket::calc_direction (SCM smob)
717 Grob *me = unsmob_grob (smob);
718 Direction dir = Tuplet_bracket::get_default_dir (me);
719 return scm_from_int (dir);
722 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
724 Tuplet_bracket::calc_positions (SCM smob)
726 Spanner *me = unsmob_spanner (smob);
730 calc_position_and_height (me, &offset, &dy);
732 SCM x = scm_cons (scm_from_double (offset),
733 scm_from_double (offset + dy));
742 Tuplet_bracket::get_default_dir (Grob *me)
744 Drul_array<int> dirs (0, 0);
745 extract_grob_set (me, "note-columns", columns);
746 for (vsize i = 0; i < columns.size (); i++)
748 Grob *nc = columns[i];
749 if (Note_column::has_rests (nc))
751 Direction d = Note_column::dir (nc);
756 if (dirs[UP] == dirs[DOWN])
760 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
763 Interval staff_extent = staff->extent (staff, Y_AXIS);
764 Interval extremal_positions;
765 extremal_positions.set_empty ();
766 for (vsize i = 0; i < columns.size (); i++)
768 Direction d = Note_column::dir (columns[i]);
769 extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
773 extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
774 while (flip (&d) != LEFT);
776 return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
779 return dirs[UP] > dirs[DOWN] ? UP : DOWN;
783 Tuplet_bracket::add_column (Grob *me, Item *n)
785 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
786 add_bound_item (dynamic_cast<Spanner *> (me), n);
790 Tuplet_bracket::add_script (Grob *me, Item *s)
792 Pointer_group_interface::add_grob (me, ly_symbol2scm ("scripts"), s);
796 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
798 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
801 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
803 Tuplet_bracket::calc_cross_staff (SCM smob)
805 Grob *me = unsmob_grob (smob);
806 extract_grob_set (me, "note-columns", cols);
807 extract_grob_set (me, "tuplets", tuplets);
809 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
810 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
811 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
812 commony = st->common_refpoint (commony, Y_AXIS);
813 if (me->check_cross_staff (commony))
816 bool equally_long = false;
817 Grob *par_beam = parallel_beam (me, cols, &equally_long);
819 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
822 for (vsize i = 0; i < cols.size (); i++)
824 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
825 if (stem && to_boolean (stem->get_property ("cross-staff")))
832 ADD_INTERFACE (Tuplet_bracket,
833 "A bracket with a number in the middle, used for tuplets."
834 " When the bracket spans a line break, the value of"
835 " @code{break-overshoot} determines how far it extends"
836 " beyond the staff. At a line break, the markups in the"
837 " @code{edge-text} are printed at the edges.",
842 "bracket-visibility "
844 "connect-to-neighbor "
848 "full-length-padding "
849 "full-length-to-extent "