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.
44 #include "tuplet-bracket.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"
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);
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));
88 Return beam that encompasses the span of the tuplet bracket.
91 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
94 Spanner *me = dynamic_cast<Spanner *> (me_grob);
96 if (me->get_bound (LEFT)->break_status_dir ()
97 || me->get_bound (RIGHT)->break_status_dir ())
100 Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
101 Note_column::get_stem (cols.back ()));
105 || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
106 != me->get_bound (RIGHT)->get_column ()))
109 Drul_array<Grob *> beams;
112 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
113 } while (flip (&d) != LEFT);
115 *equally_long = false;
116 if (!(beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
119 extract_grob_set (beams[LEFT], "stems", beam_stems);
120 if (beam_stems.size () == 0)
122 programming_error ("beam under tuplet bracket has no stems");
128 (beam_stems[0] == stems[LEFT]
129 && beam_stems.back () == stems[RIGHT]);
134 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors, 1);
136 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
138 Spanner *me = unsmob_spanner (smob);
140 Direction dir = get_grob_direction (me);
141 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
142 get_x_bound_item (me, RIGHT, dir));
144 Drul_array<bool> connect_to_other (false, false);
148 Direction break_dir = bounds[d]->break_status_dir ();
149 Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
150 vsize neighbor_idx = me->get_break_index () - break_dir;
153 && neighbor_idx < orig_spanner->broken_intos_.size ())
155 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
157 /* trigger possible suicide*/
158 (void) neighbor->get_property ("positions");
163 && neighbor_idx < orig_spanner->broken_intos_.size ()
164 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
166 while (flip (&d) != LEFT);
169 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
170 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
171 scm_from_bool (connect_to_other[RIGHT]));
177 Tuplet_bracket::get_common_x (Spanner *me)
179 extract_grob_set (me, "note-columns", columns);
181 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
182 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
183 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
188 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points, 1)
190 Tuplet_bracket::calc_control_points (SCM smob)
192 Spanner *me = unsmob_spanner (smob);
194 extract_grob_set (me, "note-columns", columns);
196 SCM scm_positions = me->get_property ("positions");
200 if (!scm_is_pair (scm_positions))
201 programming_error ("Positions should be number pair");
203 Drul_array<Real> positions
204 = robust_scm2drul (scm_positions, Drul_array<Real> (0, 0));
206 Grob *commonx = get_common_x (me);
207 Direction dir = get_grob_direction (me);
209 Drul_array<Item *> bounds;
210 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
211 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
213 Drul_array<bool> connect_to_other =
214 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
215 Drul_array<bool> (false, false));
221 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
223 if (connect_to_other[d])
225 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
226 Interval (-0.5, 0.0)));
229 x_span[d] += d * overshoot[d];
231 x_span[d] = robust_relative_extent (bounds[d],
232 commonx, X_AXIS)[RIGHT]
238 || (bounds[d]->get_column ()
239 != dynamic_cast<Item *> (columns.back ())->get_column ())))
242 We're connecting to a column, for the last bit of a broken
246 robust_scm2double(me->get_property("full-length-padding"), 1.0);
248 if (bounds[d]->break_status_dir ())
251 Real coord = bounds[d]->relative_coordinate(commonx, X_AXIS);
252 if (to_boolean (me->get_property ("full-length-to-extent")))
253 coord = robust_relative_extent(bounds[d], commonx, X_AXIS)[LEFT];
255 coord = max (coord, x_span[LEFT]);
257 x_span[d] = coord - padding;
260 while (flip (&d) != LEFT);
263 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
264 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
265 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
271 in the case that there is no bracket, but there is a (single) beam,
272 follow beam precisely for determining tuplet number location.
274 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
276 Tuplet_bracket::print (SCM smob)
278 Spanner *me = unsmob_spanner (smob);
281 extract_grob_set (me, "note-columns", columns);
282 bool equally_long = false;
283 Grob *par_beam = parallel_beam (me, columns, &equally_long);
285 bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
287 FIXME: The type of this prop is sucky.
289 SCM bracket_vis_prop = me->get_property ("bracket-visibility");
290 bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
291 bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
292 if (scm_is_bool (bracket_vis_prop))
293 bracket_visibility = bracket_prop;
295 bracket_visibility = !par_beam;
298 Don't print a tuplet bracket and number if
299 no control-points were calculated
301 SCM cpoints = me->get_property ("control-points");
302 if (scm_ilength (cpoints) < 2)
307 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
308 the bracket, but still let the number be displayed.
309 Only do this if the user has not explicitly specified bracket-visibility = #t.
313 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
314 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
316 bracket_visibility = false;
319 Drul_array<Offset> points;
320 points[LEFT] = ly_scm2offset (scm_car (cpoints));
321 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
323 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
324 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
326 Output_def *pap = me->layout ();
328 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
331 Don't print the bracket when it would be smaller than the number.
332 ...Unless the user has coded bracket-visibility = #t, that is.
335 if (bracket_visibility && number_grob)
337 Interval ext = number_grob->extent (number_grob, X_AXIS);
338 if (!ext.is_empty ())
340 gap = ext.length () + 1.0;
342 if ((0.75 * x_span.length () < gap) && !bracket_prop)
343 bracket_visibility = false;
347 if (bracket_visibility)
349 Drul_array<Real> zero (0, 0);
350 Real ss = Staff_symbol_referencer::staff_space (me);
351 Drul_array<Real> height
352 = robust_scm2drul (me->get_property ("edge-height"), zero);
353 Drul_array<Real> flare
354 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
355 Drul_array<Real> shorten
356 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
357 Drul_array<Stencil> edge_stencils;
359 Direction dir = get_grob_direction (me);
361 scale_drul (&height, -ss * dir);
362 scale_drul (&flare, ss);
363 scale_drul (&shorten, ss);
365 Drul_array<bool> connect_to_other =
366 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
367 Drul_array<bool> (false, false));
372 if (connect_to_other[d])
378 SCM edge_text = me->get_property ("edge-text");
380 if (scm_is_pair (edge_text))
382 SCM properties = Font_interface::text_font_alist_chain (me);
383 SCM text = index_get_cell (edge_text, d);
384 if (Text_interface::is_markup (text))
387 = Text_interface::interpret_markup (pap->self_scm (),
390 Stencil *edge_text = unsmob_stencil (t);
391 edge_text->translate_axis (x_span[d] - x_span[LEFT],
393 edge_stencils[d] = *edge_text;
398 while (flip (&d) != LEFT);
400 Stencil brack = make_bracket (me, Y_AXIS,
401 points[RIGHT] - points[LEFT],
404 0.1 = more space at right due to italics
405 TODO: use italic correction of font.
407 Interval (-0.5, 0.5) * gap + 0.1,
412 if (!edge_stencils[d].is_empty ())
413 brack.add_stencil (edge_stencils[d]);
415 while (flip (&d) != LEFT);
417 mol.add_stencil (brack);
420 mol.translate (points[LEFT]);
421 return mol.smobbed_copy ();
425 should move to lookup?
427 TODO: this will fail for very short (shorter than the flare)
431 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
432 Axis protrusion_axis,
434 Drul_array<Real> height,
436 Drul_array<Real> flare,
437 Drul_array<Real> shorten)
439 Drul_array<Offset> corners (Offset (0, 0), dz);
441 Real length = dz.length ();
442 Drul_array<Offset> gap_corners;
444 Axis bracket_axis = other_axis (protrusion_axis);
446 Drul_array<Offset> straight_corners = corners;
450 straight_corners[d] += -d * shorten[d] / length * dz;
451 while (flip (&d) != LEFT);
453 if (!gap.is_empty ())
456 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
457 while (flip (&d) != LEFT);
460 Drul_array<Offset> flare_corners = straight_corners;
463 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
464 flare_corners[d][protrusion_axis] += height[d];
465 straight_corners[d][bracket_axis] += -d * flare[d];
467 while (flip (&d) != LEFT);
472 if (!gap.is_empty ())
473 m.add_stencil (Line_interface::line (me, straight_corners[d],
476 m.add_stencil (Line_interface::line (me, straight_corners[d],
480 while (flip (&d) != LEFT);
483 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
484 straight_corners[RIGHT]));
490 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
492 extract_grob_set (me, "note-columns", columns);
494 while (l < columns.size () && Note_column::has_rests (columns[l]))
497 vsize r = columns.size ();
498 while (r > l && Note_column::has_rests (columns[r-1]))
506 *right = columns[r-1];
511 use first -> last note for slope, and then correct for disturbing
514 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
516 Spanner *me = dynamic_cast<Spanner *> (me_grob);
518 extract_grob_set (me, "note-columns", columns);
519 extract_grob_set (me, "tuplets", tuplets);
521 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
522 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
523 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
524 commony = st->common_refpoint (commony, Y_AXIS);
525 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
527 Grob *commonx = get_common_x (me);
528 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
531 Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
533 /* staff-padding doesn't work correctly on cross-staff tuplets
534 because it only considers one staff symbol. Until this works,
536 if (st && !to_boolean (me->get_property ("cross-staff")))
538 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
541 staff = st->extent (commony, Y_AXIS) - my_offset;
546 Direction dir = get_grob_direction (me);
548 bool equally_long = false;
549 Grob *par_beam = parallel_beam (me, columns, &equally_long);
551 Item *lgr = get_x_bound_item (me, LEFT, dir);
552 Item *rgr = get_x_bound_item (me, RIGHT, dir);
553 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
554 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
555 bool follow_beam = par_beam
556 && get_grob_direction (par_beam) == dir
557 && ! to_boolean (par_beam->get_property ("knee"));
559 vector<Offset> points;
562 && Note_column::get_stem (columns[0])
563 && Note_column::get_stem (columns.back ()))
566 trigger set_stem_ends
568 (void) par_beam->get_property ("quantized-positions");
570 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
571 Note_column::get_stem (columns.back ()));
573 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
574 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
575 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
576 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
577 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
580 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
581 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
586 Use outer non-rest columns to determine slope
590 get_bounds (me, &left_col, &right_col);
591 if (left_col && right_col)
593 Interval rv = Note_column::cross_staff_extent (right_col, commony);
594 Interval lv = Note_column::cross_staff_extent (left_col, commony);
598 Real graphical_dy = rv[dir] - lv[dir];
600 Slice ls = Note_column::head_positions_interval (left_col);
601 Slice rs = Note_column::head_positions_interval (right_col);
604 musical_dy[UP] = rs[UP] - ls[UP];
605 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
606 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
608 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
616 for (vsize i = 0; i < columns.size (); i++)
618 Interval note_ext = Note_column::cross_staff_extent (columns[i],
620 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
622 points.push_back (Offset (x, note_ext[dir]));
628 points.push_back (Offset (x0 - x0, staff[dir]));
629 points.push_back (Offset (x1 - x0, staff[dir]));
633 This is a slight hack. We compute two encompass points from the
634 bbox of the smaller tuplets.
636 We assume that the smaller bracket is 1.0 space high.
638 Real ss = Staff_symbol_referencer::staff_space (me);
639 for (vsize i = 0; i < tuplets.size (); i++)
641 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
642 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
644 if (!tuplets[i]->is_live ())
648 Drul_array<Real> positions
649 = robust_scm2interval (tuplets[i]->get_property ("positions"),
652 Real other_dy = positions[RIGHT] - positions[LEFT];
657 = tuplet_y.linear_combination (d * sign (other_dy));
660 We don't take padding into account for nested tuplets.
661 the edges can come very close to the stems, likewise for
665 points.push_back (Offset (tuplet_x[d] - x0, y));
667 while (flip (&d) != LEFT);
670 *offset = -dir * infinity_f;
671 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
672 for (vsize i = 0; i < points.size (); i++)
674 Real x = points[i][X_AXIS];
675 Real tuplety = (*dy) * x * factor + my_offset;
677 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
678 *offset = points[i][Y_AXIS] - tuplety;
681 *offset += scm_to_double (me->get_property ("padding")) * dir;
684 horizontal brackets should not collide with staff lines.
686 Kind of pointless since we put them outside the staff anyway, but
687 let's leave code for the future when possibly allow them to move
688 into the staff once again.
690 This doesn't seem to support cross-staff tuplets atm.
693 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
695 // quantize, then do collision check.
698 *offset = rint (*offset);
699 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
706 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
708 Tuplet_bracket::calc_direction (SCM smob)
710 Grob *me = unsmob_grob (smob);
711 Direction dir = Tuplet_bracket::get_default_dir (me);
712 return scm_from_int (dir);
715 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
717 Tuplet_bracket::calc_positions (SCM smob)
719 Spanner *me = unsmob_spanner (smob);
723 calc_position_and_height (me, &offset, &dy);
725 SCM x = scm_cons (scm_from_double (offset),
726 scm_from_double (offset + dy));
735 Tuplet_bracket::get_default_dir (Grob *me)
737 Drul_array<int> dirs (0, 0);
738 extract_grob_set (me, "note-columns", columns);
739 for (vsize i = 0; i < columns.size (); i++)
741 Grob *nc = columns[i];
742 Direction d = Note_column::dir (nc);
747 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
751 Tuplet_bracket::add_column (Grob *me, Item *n)
753 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
754 add_bound_item (dynamic_cast<Spanner *> (me), n);
758 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
760 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
763 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
765 Tuplet_bracket::calc_cross_staff (SCM smob)
767 Grob *me = unsmob_grob (smob);
768 extract_grob_set (me, "note-columns", cols);
769 extract_grob_set (me, "tuplets", tuplets);
771 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
772 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
773 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
774 commony = st->common_refpoint (commony, Y_AXIS);
775 if (me->check_cross_staff (commony))
778 bool equally_long = false;
779 Grob *par_beam = parallel_beam (me, cols, &equally_long);
781 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
784 for (vsize i = 0; i < cols.size (); i++)
786 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
787 if (stem && to_boolean (stem->get_property ("cross-staff")))
794 ADD_INTERFACE (Tuplet_bracket,
795 "A bracket with a number in the middle, used for tuplets."
796 " When the bracket spans a line break, the value of"
797 " @code{break-overshoot} determines how far it extends"
798 " beyond the staff. At a line break, the markups in the"
799 " @code{edge-text} are printed at the edges.",
803 "bracket-visibility "
805 "connect-to-neighbor "
810 "full-length-padding "
811 "full-length-to-extent "