2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2009 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);
287 Fixme: the type of this prop is sucky.
289 SCM bracket = me->get_property ("bracket-visibility");
290 if (scm_is_bool (bracket))
291 bracket_visibility = ly_scm2bool (bracket);
292 else if (bracket == ly_symbol2scm ("if-no-beam"))
293 bracket_visibility = !par_beam;
296 Don't print a tuplet bracket and number if
297 no control-points were calculated
299 SCM cpoints = me->get_property ("control-points");
300 if (scm_ilength (cpoints) < 2)
305 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
306 the bracket, but still let the number be displayed */
307 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
308 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
310 bracket_visibility = false;
313 Drul_array<Offset> points;
314 points[LEFT] = ly_scm2offset (scm_car (cpoints));
315 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
317 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
318 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
320 Output_def *pap = me->layout ();
322 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
325 No bracket when it would be smaller than the number.
328 if (bracket_visibility && number_grob)
330 Interval ext = number_grob->extent (number_grob, X_AXIS);
331 if (!ext.is_empty ())
333 gap = ext.length () + 1.0;
335 if (0.75 * x_span.length () < gap)
336 bracket_visibility = false;
340 if (bracket_visibility)
342 Drul_array<Real> zero (0, 0);
343 Real ss = Staff_symbol_referencer::staff_space (me);
344 Drul_array<Real> height
345 = robust_scm2drul (me->get_property ("edge-height"), zero);
346 Drul_array<Real> flare
347 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
348 Drul_array<Real> shorten
349 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
350 Drul_array<Stencil> edge_stencils;
352 Direction dir = get_grob_direction (me);
354 scale_drul (&height, -ss * dir);
355 scale_drul (&flare, ss);
356 scale_drul (&shorten, ss);
358 Drul_array<bool> connect_to_other =
359 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
360 Drul_array<bool> (false, false));
365 if (connect_to_other[d])
371 SCM edge_text = me->get_property ("edge-text");
373 if (scm_is_pair (edge_text))
375 SCM properties = Font_interface::text_font_alist_chain (me);
376 SCM text = index_get_cell (edge_text, d);
377 if (Text_interface::is_markup (text))
380 = Text_interface::interpret_markup (pap->self_scm (),
383 Stencil *edge_text = unsmob_stencil (t);
384 edge_text->translate_axis (x_span[d] - x_span[LEFT],
386 edge_stencils[d] = *edge_text;
391 while (flip (&d) != LEFT);
393 Stencil brack = make_bracket (me, Y_AXIS,
394 points[RIGHT] - points[LEFT],
397 0.1 = more space at right due to italics
398 TODO: use italic correction of font.
400 Interval (-0.5, 0.5) * gap + 0.1,
405 if (!edge_stencils[d].is_empty ())
406 brack.add_stencil (edge_stencils[d]);
408 while (flip (&d) != LEFT);
410 mol.add_stencil (brack);
413 mol.translate (points[LEFT]);
414 return mol.smobbed_copy ();
418 should move to lookup?
420 TODO: this will fail for very short (shorter than the flare)
424 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
425 Axis protrusion_axis,
427 Drul_array<Real> height,
429 Drul_array<Real> flare,
430 Drul_array<Real> shorten)
432 Drul_array<Offset> corners (Offset (0, 0), dz);
434 Real length = dz.length ();
435 Drul_array<Offset> gap_corners;
437 Axis bracket_axis = other_axis (protrusion_axis);
439 Drul_array<Offset> straight_corners = corners;
443 straight_corners[d] += -d * shorten[d] / length * dz;
444 while (flip (&d) != LEFT);
446 if (!gap.is_empty ())
449 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
450 while (flip (&d) != LEFT);
453 Drul_array<Offset> flare_corners = straight_corners;
456 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
457 flare_corners[d][protrusion_axis] += height[d];
458 straight_corners[d][bracket_axis] += -d * flare[d];
460 while (flip (&d) != LEFT);
465 if (!gap.is_empty ())
466 m.add_stencil (Line_interface::line (me, straight_corners[d],
469 m.add_stencil (Line_interface::line (me, straight_corners[d],
473 while (flip (&d) != LEFT);
476 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
477 straight_corners[RIGHT]));
483 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
485 extract_grob_set (me, "note-columns", columns);
487 while (l < columns.size () && Note_column::has_rests (columns[l]))
490 vsize r = columns.size ();
491 while (r > l && Note_column::has_rests (columns[r-1]))
499 *right = columns[r-1];
504 use first -> last note for slope, and then correct for disturbing
507 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
509 Spanner *me = dynamic_cast<Spanner *> (me_grob);
511 extract_grob_set (me, "note-columns", columns);
512 extract_grob_set (me, "tuplets", tuplets);
514 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
515 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
516 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
517 commony = st->common_refpoint (commony, Y_AXIS);
518 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
520 Grob *commonx = get_common_x (me);
521 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
524 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,
529 if (st && !to_boolean (me->get_property ("cross-staff")))
531 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
534 staff = st->extent (commony, Y_AXIS) - my_offset;
539 Direction dir = get_grob_direction (me);
541 bool equally_long = false;
542 Grob *par_beam = parallel_beam (me, columns, &equally_long);
544 Item *lgr = get_x_bound_item (me, LEFT, dir);
545 Item *rgr = get_x_bound_item (me, RIGHT, dir);
546 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
547 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
548 bool follow_beam = par_beam
549 && get_grob_direction (par_beam) == dir
550 && ! to_boolean (par_beam->get_property ("knee"));
552 vector<Offset> points;
555 && Note_column::get_stem (columns[0])
556 && Note_column::get_stem (columns.back ()))
559 trigger set_stem_ends
561 (void) par_beam->get_property ("quantized-positions");
563 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
564 Note_column::get_stem (columns.back ()));
566 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
567 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
568 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
569 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
570 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
573 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
574 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
579 Use outer non-rest columns to determine slope
583 get_bounds (me, &left_col, &right_col);
584 if (left_col && right_col)
586 Interval rv = Note_column::cross_staff_extent (right_col, commony);
587 Interval lv = Note_column::cross_staff_extent (left_col, commony);
591 Real graphical_dy = rv[dir] - lv[dir];
593 Slice ls = Note_column::head_positions_interval (left_col);
594 Slice rs = Note_column::head_positions_interval (right_col);
597 musical_dy[UP] = rs[UP] - ls[UP];
598 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
599 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
601 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
609 for (vsize i = 0; i < columns.size (); i++)
611 Interval note_ext = Note_column::cross_staff_extent (columns[i],
613 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
615 points.push_back (Offset (x, note_ext[dir]));
621 points.push_back (Offset (x0 - x0, staff[dir]));
622 points.push_back (Offset (x1 - x0, staff[dir]));
626 This is a slight hack. We compute two encompass points from the
627 bbox of the smaller tuplets.
629 We assume that the smaller bracket is 1.0 space high.
631 Real ss = Staff_symbol_referencer::staff_space (me);
632 for (vsize i = 0; i < tuplets.size (); i++)
634 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
635 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
637 if (!tuplets[i]->is_live ())
641 Drul_array<Real> positions
642 = robust_scm2interval (tuplets[i]->get_property ("positions"),
645 Real other_dy = positions[RIGHT] - positions[LEFT];
650 = tuplet_y.linear_combination (d * sign (other_dy));
653 We don't take padding into account for nested tuplets.
654 the edges can come very close to the stems, likewise for
658 points.push_back (Offset (tuplet_x[d] - x0, y));
660 while (flip (&d) != LEFT);
663 *offset = -dir * infinity_f;
664 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
665 for (vsize i = 0; i < points.size (); i++)
667 Real x = points[i][X_AXIS];
668 Real tuplety = (*dy) * x * factor + my_offset;
670 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
671 *offset = points[i][Y_AXIS] - tuplety;
674 *offset += scm_to_double (me->get_property ("padding")) * dir;
677 horizontal brackets should not collide with staff lines.
679 Kind of pointless since we put them outside the staff anyway, but
680 let's leave code for the future when possibly allow them to move
681 into the staff once again.
683 This doesn't seem to support cross-staff tuplets atm.
686 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
688 // quantize, then do collision check.
691 *offset = rint (*offset);
692 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
699 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
701 Tuplet_bracket::calc_direction (SCM smob)
703 Grob *me = unsmob_grob (smob);
704 Direction dir = Tuplet_bracket::get_default_dir (me);
705 return scm_from_int (dir);
708 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
710 Tuplet_bracket::calc_positions (SCM smob)
712 Spanner *me = unsmob_spanner (smob);
716 calc_position_and_height (me, &offset, &dy);
718 SCM x = scm_cons (scm_from_double (offset),
719 scm_from_double (offset + dy));
728 Tuplet_bracket::get_default_dir (Grob *me)
730 Drul_array<int> dirs (0, 0);
731 extract_grob_set (me, "note-columns", columns);
732 for (vsize i = 0; i < columns.size (); i++)
734 Grob *nc = columns[i];
735 Direction d = Note_column::dir (nc);
740 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
744 Tuplet_bracket::add_column (Grob *me, Item *n)
746 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
747 add_bound_item (dynamic_cast<Spanner *> (me), n);
751 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
753 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
756 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
758 Tuplet_bracket::calc_cross_staff (SCM smob)
760 Grob *me = unsmob_grob (smob);
761 extract_grob_set (me, "note-columns", cols);
762 extract_grob_set (me, "tuplets", tuplets);
764 Grob *commony = common_refpoint_of_array (cols, me, Y_AXIS);
765 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
766 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
767 commony = st->common_refpoint (commony, Y_AXIS);
768 if (me->check_cross_staff (commony))
771 bool equally_long = false;
772 Grob *par_beam = parallel_beam (me, cols, &equally_long);
774 if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
777 for (vsize i = 0; i < cols.size (); i++)
779 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
780 if (stem && to_boolean (stem->get_property ("cross-staff")))
787 ADD_INTERFACE (Tuplet_bracket,
788 "A bracket with a number in the middle, used for tuplets."
789 " When the bracket spans a line break, the value of"
790 " @code{break-overshoot} determines how far it extends"
791 " beyond the staff. At a line break, the markups in the"
792 " @code{edge-text} are printed at the edges.",
796 "bracket-visibility "
798 "connect-to-neighbor "
803 "full-length-padding "
804 "full-length-to-extent "