2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2007 Jan Nieuwenhuizen <janneke@gnu.org>
7 Han-Wen Nienhuys <hanwen@xs4all.nl>
13 - tuplet bracket should probably be subject to the same rules as
14 beam sloping/quanting.
16 - There is no support for kneed brackets, or nested brackets.
18 - number placement for parallel beams should be much more advanced:
19 for sloped beams some extra horizontal offset must be introduced.
21 - number placement is usually done over the center note, not the
26 TODO: quantise, we don't want to collide with staff lines.
27 (or should we be above staff?)
29 todo: handle breaking elegantly.
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
37 #include "output-def.hh"
38 #include "font-interface.hh"
39 #include "text-interface.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
45 #include "staff-symbol-referencer.hh"
47 #include "paper-column.hh"
51 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
53 Spanner *me = dynamic_cast<Spanner *> (me_grob);
54 Item *g = me->get_bound (hdir);
55 if (Note_column::has_interface (g)
56 && Note_column::get_stem (g)
57 && Note_column::dir (g) == my_dir)
58 g = Note_column::get_stem (g);
65 flatten_number_pair_property (Grob *me,
66 Direction xdir, SCM sym)
68 Drul_array<Real> zero (0, 0);
70 = robust_scm2drul (me->internal_get_property (sym), zero);
73 me->set_property (sym, ly_interval2scm (pair));
78 Return beam that encompasses the span of the tuplet bracket.
82 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
85 Spanner *me = dynamic_cast<Spanner *> (me_grob);
87 if (me->get_bound (LEFT)->break_status_dir ()
88 || me->get_bound (RIGHT)->break_status_dir ())
91 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
92 Note_column::get_stem (cols.back ()));
96 || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
97 != me->get_bound (RIGHT)->get_column ()))
100 Drul_array<Grob*> beams;
103 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
104 } while (flip (&d) != LEFT);
106 *equally_long = false;
107 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
110 extract_grob_set (beams[LEFT], "stems", beam_stems);
111 if (beam_stems.size () == 0)
113 programming_error ("beam under tuplet bracket has no stems");
119 (beam_stems[0] == stems[LEFT]
120 && beam_stems.back () == stems[RIGHT]);
125 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
127 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
129 Spanner *me = unsmob_spanner (smob);
131 Direction dir = get_grob_direction (me);
132 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
133 get_x_bound_item (me, RIGHT, dir));
135 Drul_array<bool> connect_to_other (false, false);
139 Direction break_dir = bounds[d]->break_status_dir ();
140 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
141 vsize neighbor_idx = me->get_break_index () - break_dir;
144 && neighbor_idx < orig_spanner->broken_intos_.size ())
146 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
148 /* trigger possible suicide*/
149 (void) neighbor->get_property ("positions");
154 && neighbor_idx < orig_spanner->broken_intos_.size ()
155 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
157 while (flip (&d) != LEFT);
160 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
161 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
162 scm_from_bool (connect_to_other[RIGHT]));
168 Tuplet_bracket::get_common_x (Spanner *me)
170 extract_grob_set (me, "note-columns", columns);
172 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
173 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
174 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
179 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
181 Tuplet_bracket::calc_control_points (SCM smob)
183 Spanner *me = unsmob_spanner (smob);
185 extract_grob_set (me, "note-columns", columns);
187 SCM scm_positions = me->get_property ("positions");
191 if (!scm_is_pair (scm_positions))
192 programming_error ("Positions should be number pair");
194 Drul_array<Real> positions
195 = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
197 Grob *commonx = get_common_x (me);
198 Direction dir = get_grob_direction (me);
200 Drul_array<Item *> bounds;
201 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
202 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
204 Drul_array<bool> connect_to_other =
205 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
206 Drul_array<bool> (false, false));
213 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
215 if (connect_to_other[d])
217 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
218 Interval (-0.5, 0.0)));
221 x_span[d] += d * overshoot[d];
223 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
229 || (bounds[d]->get_column ()
230 != dynamic_cast<Item *> (columns.back ())->get_column ())))
233 We're connecting to a column, for the last bit of a broken
236 TODO: make padding tunable?
240 if (bounds[d]->break_status_dir ())
244 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
248 while (flip (&d) != LEFT);
252 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
253 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
254 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
260 in the case that there is no bracket, but there is a (single) beam,
261 follow beam precisely for determining tuplet number location.
263 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
265 Tuplet_bracket::print (SCM smob)
267 Spanner *me = unsmob_spanner (smob);
270 extract_grob_set (me, "note-columns", columns);
271 bool equally_long = false;
272 Grob *par_beam = parallel_beam (me, columns, &equally_long);
274 bool bracket_visibility = !(par_beam && equally_long);
276 Fixme: the type of this prop is sucky.
278 SCM bracket = me->get_property ("bracket-visibility");
279 if (scm_is_bool (bracket))
280 bracket_visibility = ly_scm2bool (bracket);
281 else if (bracket == ly_symbol2scm ("if-no-beam"))
282 bracket_visibility = !par_beam;
284 SCM cpoints = me->get_property ("control-points");
285 if (scm_ilength (cpoints) < 2)
291 Drul_array<Offset> points;
292 points[LEFT] = ly_scm2offset (scm_car (cpoints));
293 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
295 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
296 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
298 Output_def *pap = me->layout ();
300 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
303 No bracket when it would be smaller than the number.
306 if (bracket_visibility && number_grob)
308 Interval ext = number_grob->extent (number_grob, X_AXIS);
309 if (!ext.is_empty ())
311 gap = ext.length () + 1.0;
313 if (0.75 * x_span.length () < gap)
314 bracket_visibility = false;
318 if (bracket_visibility)
320 Drul_array<Real> zero (0, 0);
321 Real ss = Staff_symbol_referencer::staff_space (me);
322 Drul_array<Real> height
323 = robust_scm2drul (me->get_property ("edge-height"), zero);
324 Drul_array<Real> flare
325 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
326 Drul_array<Real> shorten
327 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
328 Drul_array<Stencil> edge_stencils;
330 Direction dir = get_grob_direction (me);
332 scale_drul (&height, -ss * dir);
333 scale_drul (&flare, ss);
334 scale_drul (&shorten, ss);
336 Drul_array<bool> connect_to_other =
337 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
338 Drul_array<bool> (false, false));
343 if (connect_to_other[d])
349 SCM edge_text = me->get_property ("edge-text");
351 if (scm_is_pair (edge_text))
353 SCM properties = Font_interface::text_font_alist_chain (me);
354 SCM text = index_get_cell (edge_text, d);
355 if (Text_interface::is_markup (text))
357 SCM t = Text_interface::interpret_markup (pap->self_scm (),
360 Stencil *edge_text = unsmob_stencil (t);
361 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
362 edge_stencils[d] = *edge_text;
367 while (flip (&d) != LEFT);
369 Stencil brack = make_bracket (me, Y_AXIS,
370 points[RIGHT] - points[LEFT],
373 0.1 = more space at right due to italics
374 TODO: use italic correction of font.
376 Interval (-0.5, 0.5) * gap + 0.1,
381 if (!edge_stencils[d].is_empty ())
382 brack.add_stencil (edge_stencils[d]);
384 while (flip (&d) != LEFT);
386 mol.add_stencil (brack);
389 mol.translate (points[LEFT]);
390 return mol.smobbed_copy ();
394 should move to lookup?
396 TODO: this will fail for very short (shorter than the flare)
400 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
403 Drul_array<Real> height,
405 Drul_array<Real> flare,
406 Drul_array<Real> shorten)
408 Drul_array<Offset> corners (Offset (0, 0), dz);
410 Real length = dz.length ();
411 Drul_array<Offset> gap_corners;
413 Axis bracket_axis = other_axis (protusion_axis);
415 Drul_array<Offset> straight_corners = corners;
419 straight_corners[d] += -d * shorten[d] / length * dz;
420 while (flip (&d) != LEFT);
422 if (!gap.is_empty ())
425 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
426 while (flip (&d) != LEFT);
429 Drul_array<Offset> flare_corners = straight_corners;
432 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
433 flare_corners[d][protusion_axis] += height[d];
434 straight_corners[d][bracket_axis] += -d * flare[d];
436 while (flip (&d) != LEFT);
441 if (!gap.is_empty ())
442 m.add_stencil (Line_interface::line (me, straight_corners[d],
445 m.add_stencil (Line_interface::line (me, straight_corners[d],
449 while (flip (&d) != LEFT);
452 m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
453 straight_corners[RIGHT]));
459 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
461 extract_grob_set (me, "note-columns", columns);
463 while (l < columns.size () && Note_column::has_rests (columns[l]))
466 vsize r = columns.size ();
467 while (r > l && Note_column::has_rests (columns[r-1]))
475 *right = columns[r-1];
480 use first -> last note for slope, and then correct for disturbing
483 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
485 Spanner *me = dynamic_cast<Spanner *> (me_grob);
487 extract_grob_set (me, "note-columns", columns);
488 extract_grob_set (me, "tuplets", tuplets);
490 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
491 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
492 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
493 commony = st->common_refpoint (commony, Y_AXIS);
494 Real my_offset = me->relative_coordinate (commony, Y_AXIS);
496 Grob *commonx = get_common_x (me);
497 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
500 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
502 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
505 staff = st->extent (commony, Y_AXIS) - my_offset;
510 Direction dir = get_grob_direction (me);
512 bool equally_long = false;
513 Grob *par_beam = parallel_beam (me, columns, &equally_long);
517 Item *lgr = get_x_bound_item (me, LEFT, dir);
518 Item *rgr = get_x_bound_item (me, RIGHT, dir);
519 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
520 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
521 bool follow_beam = par_beam
522 && ((get_grob_direction (par_beam) == dir) || to_boolean (par_beam->get_property ("knee")));
524 vector<Offset> points;
528 && Note_column::get_stem (columns[0])
529 && Note_column::get_stem (columns.back ()))
532 trigger set_stem_ends
534 (void) par_beam->get_property ("quantized-positions");
537 Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
538 Note_column::get_stem (columns.back ()));
543 Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
544 Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
545 + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
546 Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
547 + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS) - my_offset;
550 points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
551 points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
556 Use outer non-rest columns to determine slope
560 get_bounds (me, &left_col, &right_col);
561 if (left_col && right_col)
563 Interval rv = right_col->extent (commony, Y_AXIS);
564 Interval lv = left_col->extent (commony, Y_AXIS);
567 Real graphical_dy = rv[dir] - lv[dir];
569 Slice ls = Note_column::head_positions_interval (left_col);
570 Slice rs = Note_column::head_positions_interval (right_col);
573 musical_dy[UP] = rs[UP] - ls[UP];
574 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
575 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
577 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
585 for (vsize i = 0; i < columns.size (); i++)
587 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
588 Real notey = note_ext[dir] - my_offset;
590 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
591 points.push_back (Offset (x, notey));
597 points.push_back (Offset (x0 - x0, staff[dir]));
598 points.push_back (Offset (x1 - x0, staff[dir]));
602 This is a slight hack. We compute two encompass points from the
603 bbox of the smaller tuplets.
605 We assume that the smaller bracket is 1.0 space high.
607 Real ss = Staff_symbol_referencer::staff_space (me);
608 for (vsize i = 0; i < tuplets.size (); i++)
610 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
611 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
613 if (!tuplets[i]->is_live ())
617 Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
621 Real other_dy = positions[RIGHT] - positions[LEFT];
626 = tuplet_y.linear_combination (d * sign (other_dy));
629 We don't take padding into account for nested tuplets.
630 the edges can come very close to the stems, likewise for
634 points.push_back (Offset (tuplet_x[d] - x0, y));
636 while (flip (&d) != LEFT);
639 *offset = -dir * infinity_f;
640 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
641 for (vsize i = 0; i < points.size (); i++)
643 Real x = points[i][X_AXIS];
644 Real tuplety = (*dy) * x * factor;
646 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
647 *offset = points[i][Y_AXIS] - tuplety;
650 *offset += scm_to_double (me->get_property ("padding")) * dir;
653 horizontal brackets should not collide with staff lines.
655 Kind of pointless since we put them outside the staff anyway, but
656 let's leave code for the future when possibly allow them to move
657 into the staff once again.
659 This doesn't seem to support cross-staff tuplets atm.
662 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
664 // quantize, then do collision check.
667 *offset = rint (*offset);
668 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
676 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
678 Tuplet_bracket::calc_direction (SCM smob)
680 Grob *me = unsmob_grob (smob);
681 Direction dir = Tuplet_bracket::get_default_dir (me);
682 return scm_from_int (dir);
685 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
687 Tuplet_bracket::calc_positions (SCM smob)
689 Spanner *me = unsmob_spanner (smob);
692 Don't print if it doesn't span time.
694 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
695 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
703 calc_position_and_height (me, &offset, &dy);
705 SCM x = scm_cons (scm_from_double (offset),
706 scm_from_double (offset + dy));
715 Tuplet_bracket::get_default_dir (Grob *me)
717 Drul_array<int> dirs (0, 0);
718 extract_grob_set (me, "note-columns", columns);
719 for (vsize i = 0; i < columns.size (); i++)
721 Grob *nc = columns[i];
722 Direction d = Note_column::dir (nc);
727 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
731 Tuplet_bracket::add_column (Grob *me, Item *n)
733 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
734 add_bound_item (dynamic_cast<Spanner *> (me), n);
738 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
740 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
743 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
745 Tuplet_bracket::calc_cross_staff (SCM smob)
747 Grob *me = unsmob_grob (smob);
748 Grob *staff_symbol = 0;
749 extract_grob_set (me, "note-columns", cols);
750 bool equally_long = false;
751 Grob *par_beam = parallel_beam (me, cols, &equally_long);
754 return par_beam->get_property ("cross-staff");
756 for (vsize i = 0; i < cols.size (); i++)
758 Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
762 if (to_boolean (stem->get_property ("cross-staff")))
765 Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
766 if (staff_symbol && (stem_staff != staff_symbol))
768 staff_symbol = stem_staff;
773 ADD_INTERFACE (Tuplet_bracket,
774 "A bracket with a number in the middle, used for tuplets. "
775 "When the bracket spans a line break, the value of "
776 "@code{break-overshoot} determines how far it extends "
778 "At a line break, the markups in the @code{edge-text} are printed "
784 "bracket-visibility "
786 "connect-to-neighbor "