2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2010 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 = to_boolean (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
285 bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
287 bracket_visibility = true;
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.
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
506 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
508 Spanner *me = dynamic_cast<Spanner *> (me_grob);
510 extract_grob_set (me, "note-columns", columns);
511 extract_grob_set (me, "tuplets", tuplets);
513 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
514 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
515 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
516 commony = st->common_refpoint (commony, Y_AXIS);
517 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
519 Grob *commonx = get_common_x (me);
520 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
523 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
526 staff-padding doesn't work correctly on cross-staff tuplets
527 because it only considers one staff symbol. Until this works,
530 if (st && !to_boolean (me->get_property ("cross-staff")))
532 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
535 staff = st->extent (commony, Y_AXIS) - my_offset;
540 Direction dir = get_grob_direction (me);
542 bool equally_long = false;
543 Grob *par_beam = parallel_beam (me, columns, &equally_long);
545 Item *lgr = get_x_bound_item (me, LEFT, dir);
546 Item *rgr = get_x_bound_item (me, RIGHT, dir);
547 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
548 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
549 bool follow_beam = par_beam
550 && get_grob_direction (par_beam) == dir
551 && ! to_boolean (par_beam->get_property ("knee"));
553 vector<Offset> points;
556 && Note_column::get_stem (columns[0])
557 && Note_column::get_stem (columns.back ()))
560 trigger set_stem_ends
562 (void) par_beam->get_property ("quantized-positions");
564 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
565 Note_column::get_stem (columns.back ()));
567 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
568 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
569 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
570 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
571 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
574 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
575 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
580 Use outer non-rest columns to determine slope
584 get_bounds (me, &left_col, &right_col);
585 if (left_col && right_col)
587 Interval rv = Note_column::cross_staff_extent (right_col, commony);
588 Interval lv = Note_column::cross_staff_extent (left_col, commony);
592 Real graphical_dy = rv[dir] - lv[dir];
594 Slice ls = Note_column::head_positions_interval (left_col);
595 Slice rs = Note_column::head_positions_interval (right_col);
598 musical_dy[UP] = rs[UP] - ls[UP];
599 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
600 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
602 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
610 for (vsize i = 0; i < columns.size (); i++)
612 Interval note_ext = Note_column::cross_staff_extent (columns[i],
614 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
616 points.push_back (Offset (x, note_ext[dir]));
622 points.push_back (Offset (x0 - x0, staff[dir]));
623 points.push_back (Offset (x1 - x0, staff[dir]));
627 This is a slight hack. We compute two encompass points from the
628 bbox of the smaller tuplets.
630 We assume that the smaller bracket is 1.0 space high.
632 Real ss = Staff_symbol_referencer::staff_space (me);
633 for (vsize i = 0; i < tuplets.size (); i++)
635 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
636 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
638 if (!tuplets[i]->is_live ())
642 Drul_array<Real> positions
643 = robust_scm2interval (tuplets[i]->get_property ("positions"),
646 Real other_dy = positions[RIGHT] - positions[LEFT];
651 = tuplet_y.linear_combination (d * sign (other_dy));
654 We don't take padding into account for nested tuplets.
655 the edges can come very close to the stems, likewise for
659 points.push_back (Offset (tuplet_x[d] - x0, y));
661 while (flip (&d) != LEFT);
664 *offset = -dir * infinity_f;
665 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
666 for (vsize i = 0; i < points.size (); i++)
668 Real x = points[i][X_AXIS];
669 Real tuplety = (*dy) * x * factor + my_offset;
671 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
672 *offset = points[i][Y_AXIS] - tuplety;
675 *offset += scm_to_double (me->get_property ("padding")) * dir;
678 horizontal brackets should not collide with staff lines.
680 Kind of pointless since we put them outside the staff anyway, but
681 let's leave code for the future when possibly allow them to move
682 into the staff once again.
684 This doesn't seem to support cross-staff tuplets atm.
687 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
689 // quantize, then do collision check.
692 *offset = rint (*offset);
693 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
700 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
702 Tuplet_bracket::calc_direction (SCM smob)
704 Grob *me = unsmob_grob (smob);
705 Direction dir = Tuplet_bracket::get_default_dir (me);
706 return scm_from_int (dir);
709 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
711 Tuplet_bracket::calc_positions (SCM smob)
713 Spanner *me = unsmob_spanner (smob);
717 calc_position_and_height (me, &offset, &dy);
719 SCM x = scm_cons (scm_from_double (offset),
720 scm_from_double (offset + dy));
729 Tuplet_bracket::get_default_dir (Grob *me)
731 Drul_array<int> dirs (0, 0);
732 extract_grob_set (me, "note-columns", columns);
733 for (vsize i = 0; i < columns.size (); i++)
735 Grob *nc = columns[i];
736 Direction d = Note_column::dir (nc);
741 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
745 Tuplet_bracket::add_column (Grob *me, Item *n)
747 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
748 add_bound_item (dynamic_cast<Spanner *> (me), n);
752 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
754 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
757 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
759 Tuplet_bracket::calc_cross_staff (SCM smob)
761 Grob *me = unsmob_grob (smob);
762 extract_grob_set (me, "note-columns", cols);
763 extract_grob_set (me, "tuplets", tuplets);
765 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
766 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
767 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
768 commony = st->common_refpoint (commony, Y_AXIS);
769 if (me->check_cross_staff (commony))
772 bool equally_long = false;
773 Grob *par_beam = parallel_beam (me, cols, &equally_long);
775 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
778 for (vsize i = 0; i < cols.size (); i++)
780 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
781 if (stem && to_boolean (stem->get_property ("cross-staff")))
788 ADD_INTERFACE (Tuplet_bracket,
789 "A bracket with a number in the middle, used for tuplets."
790 " When the bracket spans a line break, the value of"
791 " @code{break-overshoot} determines how far it extends"
792 " beyond the staff. At a line break, the markups in the"
793 " @code{edge-text} are printed at the edges.",
797 "bracket-visibility "
799 "connect-to-neighbor "
804 "full-length-padding "
805 "full-length-to-extent "