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 ()));
560 Direction side = LEFT;
563 // Trigger setting of stem lengths if necessary.
564 if (Grob *beam = Stem::get_beam (stems[side]))
565 (void) beam->get_property ("quantized-positions");
566 poss[side] = stems[side]->extent (stems[side], Y_AXIS)[get_grob_direction (stems[side])]
567 + stems[side]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
569 while (flip (&side) != LEFT);
571 *dy = poss[RIGHT] - poss[LEFT];
572 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, poss[LEFT]));
573 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, poss[RIGHT]));
578 Use outer non-rest columns to determine slope
582 get_bounds (me, &left_col, &right_col);
583 if (left_col && right_col)
585 Interval rv = Note_column::cross_staff_extent (right_col, commony);
586 Interval lv = Note_column::cross_staff_extent (left_col, commony);
590 Real graphical_dy = rv[dir] - lv[dir];
592 Slice ls = Note_column::head_positions_interval (left_col);
593 Slice rs = Note_column::head_positions_interval (right_col);
596 musical_dy[UP] = rs[UP] - ls[UP];
597 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
598 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
600 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
608 for (vsize i = 0; i < columns.size (); i++)
610 Interval note_ext = Note_column::cross_staff_extent (columns[i],
612 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
614 points.push_back (Offset (x, note_ext[dir]));
620 points.push_back (Offset (x0 - x0, staff[dir]));
621 points.push_back (Offset (x1 - x0, staff[dir]));
625 This is a slight hack. We compute two encompass points from the
626 bbox of the smaller tuplets.
628 We assume that the smaller bracket is 1.0 space high.
630 Real ss = Staff_symbol_referencer::staff_space (me);
631 for (vsize i = 0; i < tuplets.size (); i++)
633 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
634 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
636 if (!tuplets[i]->is_live ())
640 Drul_array<Real> positions
641 = robust_scm2interval (tuplets[i]->get_property ("positions"),
644 Real other_dy = positions[RIGHT] - positions[LEFT];
649 = tuplet_y.linear_combination (d * sign (other_dy));
652 We don't take padding into account for nested tuplets.
653 the edges can come very close to the stems, likewise for
657 points.push_back (Offset (tuplet_x[d] - x0, y));
659 while (flip (&d) != LEFT);
662 *offset = -dir * infinity_f;
663 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
664 for (vsize i = 0; i < points.size (); i++)
666 Real x = points[i][X_AXIS];
667 Real tuplety = (*dy) * x * factor + my_offset;
669 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
670 *offset = points[i][Y_AXIS] - tuplety;
673 *offset += scm_to_double (me->get_property ("padding")) * dir;
676 horizontal brackets should not collide with staff lines.
678 Kind of pointless since we put them outside the staff anyway, but
679 let's leave code for the future when possibly allow them to move
680 into the staff once again.
682 This doesn't seem to support cross-staff tuplets atm.
685 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
687 // quantize, then do collision check.
690 *offset = rint (*offset);
691 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
698 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
700 Tuplet_bracket::calc_direction (SCM smob)
702 Grob *me = unsmob_grob (smob);
703 Direction dir = Tuplet_bracket::get_default_dir (me);
704 return scm_from_int (dir);
707 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
709 Tuplet_bracket::calc_positions (SCM smob)
711 Spanner *me = unsmob_spanner (smob);
715 calc_position_and_height (me, &offset, &dy);
717 SCM x = scm_cons (scm_from_double (offset),
718 scm_from_double (offset + dy));
727 Tuplet_bracket::get_default_dir (Grob *me)
729 Drul_array<int> dirs (0, 0);
730 extract_grob_set (me, "note-columns", columns);
731 for (vsize i = 0; i < columns.size (); i++)
733 Grob *nc = columns[i];
734 if (Note_column::has_rests (nc))
736 Direction d = Note_column::dir (nc);
741 if (dirs[UP] == dirs[DOWN])
745 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
748 Interval staff_extent = staff->extent (staff, Y_AXIS);
749 Interval extremal_positions;
750 extremal_positions.set_empty ();
751 for (vsize i = 0; i < columns.size (); i++)
753 Direction d = Note_column::dir (columns[i]);
754 extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
758 extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
759 while (flip (&d) != LEFT);
761 return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
764 return dirs[UP] > dirs[DOWN] ? UP : DOWN;
768 Tuplet_bracket::add_column (Grob *me, Item *n)
770 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
771 add_bound_item (dynamic_cast<Spanner *> (me), n);
775 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
777 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
780 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
782 Tuplet_bracket::calc_cross_staff (SCM smob)
784 Grob *me = unsmob_grob (smob);
785 extract_grob_set (me, "note-columns", cols);
786 extract_grob_set (me, "tuplets", tuplets);
788 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
789 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
790 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
791 commony = st->common_refpoint (commony, Y_AXIS);
792 if (me->check_cross_staff (commony))
795 bool equally_long = false;
796 Grob *par_beam = parallel_beam (me, cols, &equally_long);
798 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
801 for (vsize i = 0; i < cols.size (); i++)
803 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
804 if (stem && to_boolean (stem->get_property ("cross-staff")))
811 ADD_INTERFACE (Tuplet_bracket,
812 "A bracket with a number in the middle, used for tuplets."
813 " When the bracket spans a line break, the value of"
814 " @code{break-overshoot} determines how far it extends"
815 " beyond the staff. At a line break, the markups in the"
816 " @code{edge-text} are printed at the edges.",
820 "bracket-visibility "
822 "connect-to-neighbor "
827 "full-length-padding "
828 "full-length-to-extent "