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 SCM scm_positions = me->get_property ("positions");
185 if (!scm_is_pair (scm_positions))
186 programming_error ("Positions should be number pair");
188 Drul_array<Real> positions
189 = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
191 Grob *commonx = get_common_x (me);
192 Direction dir = get_grob_direction (me);
194 Drul_array<Item *> bounds;
195 bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
196 bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
198 Drul_array<bool> connect_to_other =
199 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
200 Drul_array<bool> (false, false));
207 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
209 if (connect_to_other[d])
211 Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
212 Interval (-0.5, 0.0)));
215 x_span[d] += d * overshoot[d];
217 x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
223 || (bounds[d]->get_column ()
224 != dynamic_cast<Item *> (columns.back ())->get_column ())))
227 We're connecting to a column, for the last bit of a broken
230 TODO: make padding tunable?
234 if (bounds[d]->break_status_dir ())
238 = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
242 while (flip (&d) != LEFT);
246 x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
247 return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
248 ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
254 in the case that there is no bracket, but there is a (single) beam,
255 follow beam precisely for determining tuplet number location.
257 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
259 Tuplet_bracket::print (SCM smob)
261 Spanner *me = unsmob_spanner (smob);
264 extract_grob_set (me, "note-columns", columns);
265 bool equally_long = false;
266 Grob *par_beam = parallel_beam (me, columns, &equally_long);
268 bool bracket_visibility = !(par_beam && equally_long);
270 Fixme: the type of this prop is sucky.
272 SCM bracket = me->get_property ("bracket-visibility");
273 if (scm_is_bool (bracket))
274 bracket_visibility = ly_scm2bool (bracket);
275 else if (bracket == ly_symbol2scm ("if-no-beam"))
276 bracket_visibility = !par_beam;
279 SCM cpoints = me->get_property ("control-points");
280 if (scm_ilength (cpoints) < 2)
286 Drul_array<Offset> points;
287 points[LEFT] = ly_scm2offset (scm_car (cpoints));
288 points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
290 Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
291 Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
295 Output_def *pap = me->layout ();
297 Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
300 No bracket when it would be smaller than the number.
303 if (bracket_visibility && number_grob)
305 Interval ext = number_grob->extent (number_grob, X_AXIS);
306 if (!ext.is_empty ())
308 gap = ext.length () + 1.0;
310 if (0.75 * x_span.length () < gap)
311 bracket_visibility = false;
315 if (bracket_visibility)
317 Drul_array<Real> zero (0, 0);
318 Real ss = Staff_symbol_referencer::staff_space (me);
319 Drul_array<Real> height
320 = robust_scm2drul (me->get_property ("edge-height"), zero);
321 Drul_array<Real> flare
322 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
323 Drul_array<Real> shorten
324 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
325 Drul_array<Stencil> edge_stencils;
327 Direction dir = get_grob_direction (me);
329 scale_drul (&height, -ss * dir);
330 scale_drul (&flare, ss);
331 scale_drul (&shorten, ss);
333 Drul_array<bool> connect_to_other =
334 robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
335 Drul_array<bool> (false, false));
340 if (connect_to_other[d])
346 SCM edge_text = me->get_property ("edge-text");
348 if (scm_is_pair (edge_text))
350 SCM properties = Font_interface::text_font_alist_chain (me);
351 SCM text = index_get_cell (edge_text, d);
352 if (Text_interface::is_markup (text))
354 SCM t = Text_interface::interpret_markup (pap->self_scm (),
357 Stencil *edge_text = unsmob_stencil (t);
358 edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
359 edge_stencils[d] = *edge_text;
364 while (flip (&d) != LEFT);
366 Stencil brack = make_bracket (me, Y_AXIS,
367 points[RIGHT] - points[LEFT],
370 0.1 = more space at right due to italics
371 TODO: use italic correction of font.
373 Interval (-0.5, 0.5) * gap + 0.1,
378 if (!edge_stencils[d].is_empty ())
379 brack.add_stencil (edge_stencils[d]);
381 while (flip (&d) != LEFT);
383 mol.add_stencil (brack);
386 mol.translate (points[LEFT]);
387 return mol.smobbed_copy ();
391 should move to lookup?
393 TODO: this will fail for very short (shorter than the flare)
397 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
400 Drul_array<Real> height,
402 Drul_array<Real> flare,
403 Drul_array<Real> shorten)
405 Drul_array<Offset> corners (Offset (0, 0), dz);
407 Real length = dz.length ();
408 Drul_array<Offset> gap_corners;
410 Axis bracket_axis = other_axis (protusion_axis);
412 Drul_array<Offset> straight_corners = corners;
416 straight_corners[d] += -d * shorten[d] / length * dz;
417 while (flip (&d) != LEFT)
421 gap = Interval (0, 0);
423 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
424 while (flip (&d) != LEFT)
427 Drul_array<Offset> flare_corners = straight_corners;
430 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
431 flare_corners[d][protusion_axis] += height[d];
432 straight_corners[d][bracket_axis] += -d * flare[d];
434 while (flip (&d) != LEFT);
439 m.add_stencil (Line_interface::line (me, straight_corners[d],
442 m.add_stencil (Line_interface::line (me, straight_corners[d],
445 while (flip (&d) != LEFT);
451 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
453 extract_grob_set (me, "note-columns", columns);
455 while (l < columns.size () && Note_column::has_rests (columns[l]))
458 vsize r = columns.size ();
459 while (r > l && Note_column::has_rests (columns[r-1]))
467 *right = columns[r-1];
472 use first -> last note for slope, and then correct for disturbing
475 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
477 Spanner *me = dynamic_cast<Spanner *> (me_grob);
479 extract_grob_set (me, "note-columns", columns);
480 extract_grob_set (me, "tuplets", tuplets);
482 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
483 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
484 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
485 commony = st->common_refpoint (commony, Y_AXIS);
487 Grob *commonx = get_common_x (me);
488 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
491 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
493 Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
496 staff = st->extent (commony, Y_AXIS);
501 Direction dir = get_grob_direction (me);
504 Use outer non-rest columns to determine slope
508 get_bounds (me, &left_col, &right_col);
509 if (left_col && right_col)
511 Interval rv = right_col->extent (commony, Y_AXIS);
512 Interval lv = left_col->extent (commony, Y_AXIS);
515 Real graphical_dy = rv[dir] - lv[dir];
517 Slice ls = Note_column::head_positions_interval (left_col);
518 Slice rs = Note_column::head_positions_interval (right_col);
521 musical_dy[UP] = rs[UP] - ls[UP];
522 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
523 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
525 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
533 *offset = -dir * infinity_f;
535 Item *lgr = get_x_bound_item (me, LEFT, dir);
536 Item *rgr = get_x_bound_item (me, RIGHT, dir);
537 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
538 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
540 vector<Offset> points;
541 points.push_back (Offset (x0 - x0, staff[dir]));
542 points.push_back (Offset (x1 - x0, staff[dir]));
544 for (vsize i = 0; i < columns.size (); i++)
546 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
547 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
549 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
550 points.push_back (Offset (x, notey));
554 This is a slight hack. We compute two encompass points from the
555 bbox of the smaller tuplets.
557 We assume that the smaller bracket is 1.0 space high.
559 Real ss = Staff_symbol_referencer::staff_space (me);
560 for (vsize i = 0; i < tuplets.size (); i++)
562 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
563 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
566 Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
569 Real other_dy = positions[RIGHT] - positions[LEFT];
574 = tuplet_y.linear_combination (d * sign (other_dy));
577 We don't take padding into account for nested tuplets.
578 the edges can come very close to the stems, likewise for
582 points.push_back (Offset (tuplet_x[d] - x0, y));
584 while (flip (&d) != LEFT);
587 Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
588 for (vsize i = 0; i < points.size (); i++)
590 Real x = points[i][X_AXIS];
591 Real tuplety = (*dy) * x * factor;
593 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
594 *offset = points[i][Y_AXIS] - tuplety;
597 *offset += scm_to_double (me->get_property ("padding")) * dir;
600 horizontal brackets should not collide with staff lines.
602 Kind of pointless since we put them outside the staff anyway, but
603 let's leave code for the future when possibly allow them to move
604 into the staff once again.
607 && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
609 // quantize, then do collision check.
612 *offset = rint (*offset);
613 if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
621 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
623 Tuplet_bracket::calc_direction (SCM smob)
625 Grob *me = unsmob_grob (smob);
626 Direction dir = Tuplet_bracket::get_default_dir (me);
627 return scm_from_int (dir);
630 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
632 Tuplet_bracket::calc_positions (SCM smob)
634 Spanner *me = unsmob_spanner (smob);
635 extract_grob_set (me, "note-columns", columns);
638 Don't print if it doesn't span time.
640 if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
641 == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
648 Direction dir = get_grob_direction (me);
649 bool equally_long = false;
650 Grob *par_beam = parallel_beam (me, columns, &equally_long);
653 We follow the beam only if there is one, and we are next to it.
658 || get_grob_direction (par_beam) != dir)
659 calc_position_and_height (me, &offset, &dy);
662 SCM ps = par_beam->get_property ("positions");
664 Real lp = scm_to_double (scm_car (ps));
665 Real rp = scm_to_double (scm_cdr (ps));
667 Real ss = Staff_symbol_referencer::staff_space (me);
669 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
677 SCM x = scm_cons (scm_from_double (offset),
678 scm_from_double (offset + dy));
687 Tuplet_bracket::get_default_dir (Grob *me)
689 Drul_array<int> dirs (0, 0);
690 extract_grob_set (me, "note-columns", columns);
691 for (vsize i = 0; i < columns.size (); i++)
693 Grob *nc = columns[i];
694 Direction d = Note_column::dir (nc);
699 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
703 Tuplet_bracket::add_column (Grob *me, Item *n)
705 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
706 add_bound_item (dynamic_cast<Spanner *> (me), n);
710 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
712 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
715 ADD_INTERFACE (Tuplet_bracket,
716 "tuplet-bracket-interface",
717 "A bracket with a number in the middle, used for tuplets. "
718 "When the bracket spans a line break, the value of "
719 "@code{break-overshoot} determines how far it extends "
721 "At a line break, the markups in the @code{edge-text} are printed "
727 "bracket-visibility "
729 "connect-to-neighbor "