2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2006 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->internal_set_property (sym, ly_interval2scm (pair));
78 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols, bool *equally_long)
80 Spanner *me = dynamic_cast<Spanner *> (me_grob);
82 if (me->get_bound (LEFT)->break_status_dir ()
83 || me->get_bound (RIGHT)->break_status_dir ())
86 Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
87 Note_column::get_stem (cols.back ()));
89 if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
90 != me->get_bound (RIGHT)->get_column())
93 Drul_array<Grob*> beams;
96 beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
97 } while (flip (&d) != LEFT);
99 *equally_long = false;
100 if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
103 extract_grob_set (beams[LEFT], "stems", beam_stems);
104 if (beam_stems.size () == 0)
106 programming_error ("beam under tuplet bracket has no stems");
112 (beam_stems[0] == stems[LEFT] && beam_stems.back () == stems[RIGHT]);
117 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_connect_to_neighbors,1);
119 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
121 Spanner *me = unsmob_spanner (smob);
123 Direction dir = get_grob_direction (me);
124 Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
125 get_x_bound_item (me, RIGHT, dir));
127 Drul_array<bool> connect_to_other (false, false);
131 Direction break_dir = bounds[d]->break_status_dir ();
133 Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
135 vsize neighbor_idx = me->get_break_index () - break_dir;
138 && neighbor_idx < orig_spanner->broken_intos_.size ())
140 Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
142 /* trigger possible suicide*/
143 (void) neighbor->get_property ("positions");
148 && neighbor_idx < orig_spanner->broken_intos_.size ()
149 && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
151 while (flip (&d) != LEFT);
154 if (connect_to_other[LEFT] || connect_to_other[RIGHT])
155 return scm_cons (scm_from_bool (connect_to_other[LEFT]),
156 scm_from_bool (connect_to_other[RIGHT]));
162 Tuplet_bracket::get_common_x (Spanner *me)
164 extract_grob_set (me, "note-columns", columns);
166 Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
167 commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
168 commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
173 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_control_points,1)
175 Tuplet_bracket::calc_control_points (SCM smob)
177 Spanner *me = unsmob_spanner (smob);
179 extract_grob_set (me, "note-columns", columns);
181 Drul_array<Real> positions
182 = ly_scm2realdrul (me->get_property ("positions"));
184 Grob *commonx = get_common_x (me);
185 Direction dir = get_grob_direction (me);
187 Drul_array<Item *> bounds;
188 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
189 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
191 Drul_array<bool> connect_to_other =
192 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
193 Drul_array<bool> (false, false));
200 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
202 if (connect_to_other[d])
204 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
205 Interval (-0.5, 0.0)));
208 x_span[d] += d * overshoot[d];
210 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
216 || (bounds[d]->get_column ()
217 != dynamic_cast<Item *> (columns.back ())->get_column ())))
220 We're connecting to a column, for the last bit of a broken
223 TODO: make padding tunable?
227 if (bounds[d]->break_status_dir ())
231 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
235 while (flip (&d) != LEFT);
239 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
240 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
241 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
247 in the case that there is no bracket, but there is a (single) beam,
248 follow beam precisely for determining tuplet number location.
250 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
252 Tuplet_bracket::print (SCM smob)
254 Spanner *me = unsmob_spanner (smob);
257 extract_grob_set (me, "note-columns", columns);
258 bool equally_long = false;
259 Grob *par_beam = parallel_beam (me, columns, &equally_long);
261 bool bracket_visibility = !(par_beam && equally_long);
263 Fixme: the type of this prop is sucky.
265 SCM bracket = me->get_property ("bracket-visibility");
266 if (scm_is_bool (bracket))
267 bracket_visibility = ly_scm2bool (bracket);
268 else if (bracket == ly_symbol2scm ("if-no-beam"))
269 bracket_visibility = !par_beam;
272 SCM cpoints = me->get_property ("control-points");
273 if (scm_ilength (cpoints) < 2)
279 Drul_array<Offset> points;
280 points[LEFT] = ly_scm2offset (scm_car (cpoints));
281 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
283 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
284 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
288 Output_def *pap = me->layout ();
290 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
293 No bracket when it would be smaller than the number.
296 if (bracket_visibility && number_grob)
298 Interval ext = number_grob->extent (number_grob, X_AXIS);
299 if (!ext.is_empty ())
301 gap = ext.length () + 1.0;
303 if (0.75 * x_span.length () < gap)
304 bracket_visibility = false;
308 if (bracket_visibility)
310 Drul_array<Real> zero (0, 0);
311 Real ss = Staff_symbol_referencer::staff_space (me);
312 Drul_array<Real> height
313 = robust_scm2drul (me->get_property ("edge-height"), zero);
314 Drul_array<Real> flare
315 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
316 Drul_array<Real> shorten
317 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
318 Drul_array<Stencil> edge_stencils;
320 Direction dir = get_grob_direction (me);
322 scale_drul (&height, -ss * dir);
323 scale_drul (&flare, ss);
324 scale_drul (&shorten, ss);
326 Drul_array<bool> connect_to_other =
327 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
328 Drul_array<bool> (false, false));
333 if (connect_to_other[d])
339 SCM edge_text = me->get_property ("edge-text");
341 if (scm_is_pair (edge_text))
343 SCM properties = Font_interface::text_font_alist_chain (me);
344 SCM text = index_get_cell (edge_text, d);
345 if (Text_interface::is_markup (text))
347 SCM t = Text_interface::interpret_markup (pap->self_scm (),
350 Stencil *edge_text = unsmob_stencil (t);
351 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
352 edge_stencils[d] = *edge_text;
357 while (flip (&d) != LEFT);
359 Stencil brack = make_bracket (me, Y_AXIS,
360 points[RIGHT] - points[LEFT],
363 0.1 = more space at right due to italics
364 TODO: use italic correction of font.
366 Interval (-0.5, 0.5) * gap + 0.1,
371 if (!edge_stencils[d].is_empty ())
372 brack.add_stencil (edge_stencils[d]);
374 while (flip (&d) != LEFT);
376 mol.add_stencil (brack);
379 mol.translate (points[LEFT]);
380 return mol.smobbed_copy ();
384 should move to lookup?
386 TODO: this will fail for very short (shorter than the flare)
390 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
393 Drul_array<Real> height,
395 Drul_array<Real> flare,
396 Drul_array<Real> shorten)
398 Drul_array<Offset> corners (Offset (0, 0), dz);
400 Real length = dz.length ();
401 Drul_array<Offset> gap_corners;
403 Axis bracket_axis = other_axis (protusion_axis);
405 Drul_array<Offset> straight_corners = corners;
409 straight_corners[d] += -d * shorten[d] / length * dz;
410 while (flip (&d) != LEFT)
414 gap = Interval (0, 0);
416 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
417 while (flip (&d) != LEFT)
420 Drul_array<Offset> flare_corners = straight_corners;
423 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
424 flare_corners[d][protusion_axis] += height[d];
425 straight_corners[d][bracket_axis] += -d * flare[d];
427 while (flip (&d) != LEFT);
432 m.add_stencil (Line_interface::line (me, straight_corners[d],
435 m.add_stencil (Line_interface::line (me, straight_corners[d],
438 while (flip (&d) != LEFT);
444 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
446 extract_grob_set (me, "note-columns", columns);
448 while (l < columns.size () && Note_column::has_rests (columns[l]))
451 vsize r = columns.size ();
452 while (r > l && Note_column::has_rests (columns[r-1]))
460 *right = columns[r-1];
465 use first -> last note for slope, and then correct for disturbing
468 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
470 Spanner *me = dynamic_cast<Spanner *> (me_grob);
472 extract_grob_set (me, "note-columns", columns);
473 extract_grob_set (me, "tuplets", tuplets);
475 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
476 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
477 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
478 commony = st->common_refpoint (commony, Y_AXIS);
480 Grob *commonx = get_common_x (me);
481 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
484 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
486 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
489 staff = st->extent (commony, Y_AXIS);
494 Direction dir = get_grob_direction (me);
497 Use outer non-rest columns to determine slope
501 get_bounds (me, &left_col, &right_col);
502 if (left_col && right_col)
504 Interval rv = right_col->extent (commony, Y_AXIS);
505 Interval lv = left_col->extent (commony, Y_AXIS);
508 Real graphical_dy = rv[dir] - lv[dir];
510 Slice ls = Note_column::head_positions_interval (left_col);
511 Slice rs = Note_column::head_positions_interval (right_col);
514 musical_dy[UP] = rs[UP] - ls[UP];
515 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
516 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
518 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
526 *offset = -dir * infinity_f;
528 Item *lgr = get_x_bound_item (me, LEFT, dir);
529 Item *rgr = get_x_bound_item (me, RIGHT, dir);
530 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
531 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
533 vector<Offset> points;
534 points.push_back (Offset (x0 - x0, staff[dir]));
535 points.push_back (Offset (x1 - x0, staff[dir]));
537 for (vsize i = 0; i < columns.size (); i++)
539 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
540 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
542 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
543 points.push_back (Offset (x, notey));
547 This is a slight hack. We compute two encompass points from the
548 bbox of the smaller tuplets.
550 We assume that the smaller bracket is 1.0 space high.
552 Real ss = Staff_symbol_referencer::staff_space (me);
553 for (vsize i = 0; i < tuplets.size (); i++)
555 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
556 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
559 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
562 Real other_dy = positions[RIGHT] - positions[LEFT];
567 = tuplet_y.linear_combination (d * sign (other_dy));
570 We don't take padding into account for nested tuplets.
571 the edges can come very close to the stems, likewise for
575 points.push_back (Offset (tuplet_x[d] - x0, y));
577 while (flip (&d) != LEFT);
580 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
581 for (vsize i = 0; i < points.size (); i++)
583 Real x = points[i][X_AXIS];
584 Real tuplety = (*dy) * x * factor;
586 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
587 *offset = points[i][Y_AXIS] - tuplety;
590 *offset += scm_to_double (me->get_property ("padding")) * dir;
593 horizontal brackets should not collide with staff lines.
595 Kind of pointless since we put them outside the staff anyway, but
596 let's leave code for the future when possibly allow them to move
597 into the staff once again.
600 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
602 // quantize, then do collision check.
605 *offset = rint (*offset);
606 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
614 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
616 Tuplet_bracket::calc_direction (SCM smob)
618 Grob *me = unsmob_grob (smob);
619 Direction dir = Tuplet_bracket::get_default_dir (me);
620 return scm_from_int (dir);
623 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
625 Tuplet_bracket::calc_positions (SCM smob)
627 Spanner *me = unsmob_spanner (smob);
628 extract_grob_set (me, "note-columns", columns);
632 Don't print if it doesn't span time.
634 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
635 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
642 Direction dir = get_grob_direction (me);
643 bool equally_long = false;
644 Grob *par_beam = parallel_beam (me, columns, &equally_long);
647 We follow the beam only if there is one, and we are next to it.
652 || get_grob_direction (par_beam) != dir)
653 calc_position_and_height (me, &offset, &dy);
656 SCM ps = par_beam->get_property ("positions");
658 Real lp = scm_to_double (scm_car (ps));
659 Real rp = scm_to_double (scm_cdr (ps));
661 Real ss = Staff_symbol_referencer::staff_space (me);
663 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
671 SCM x = scm_cons (scm_from_double (offset),
672 scm_from_double (offset + dy));
681 Tuplet_bracket::get_default_dir (Grob *me)
683 Drul_array<int> dirs (0, 0);
684 extract_grob_set (me, "note-columns", columns);
685 for (vsize i = 0; i < columns.size (); i++)
687 Grob *nc = columns[i];
688 Direction d = Note_column::dir (nc);
693 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
697 Tuplet_bracket::add_column (Grob *me, Item *n)
699 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
700 add_bound_item (dynamic_cast<Spanner *> (me), n);
704 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
706 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
709 ADD_INTERFACE (Tuplet_bracket,
710 "tuplet-bracket-interface",
711 "A bracket with a number in the middle, used for tuplets. "
712 "When the bracket spans a line break, the value of "
713 "@code{break-overshoot} determines how far it extends "
715 "At a line break, the markups in the @code{edge-text} are printed "
721 "bracket-visibility "
723 "connect-to-neighbor "