2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2009 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
, Direction xdir
, SCM sym
)
67 Drul_array
<Real
> zero (0, 0);
69 = robust_scm2drul (me
->internal_get_property (sym
), zero
);
72 me
->set_property (sym
, ly_interval2scm (pair
));
77 Return beam that encompasses the span of the tuplet bracket.
80 Tuplet_bracket::parallel_beam (Grob
*me_grob
, vector
<Grob
*> const &cols
,
83 Spanner
*me
= dynamic_cast<Spanner
*> (me_grob
);
85 if (me
->get_bound (LEFT
)->break_status_dir ()
86 || me
->get_bound (RIGHT
)->break_status_dir ())
89 Drul_array
<Grob
*> stems (Note_column::get_stem (cols
[0]),
90 Note_column::get_stem (cols
.back ()));
94 || (dynamic_cast<Item
*> (stems
[RIGHT
])->get_column ()
95 != me
->get_bound (RIGHT
)->get_column ()))
98 Drul_array
<Grob
*> beams
;
101 beams
[d
] = stems
[d
] ? Stem::get_beam (stems
[d
]) : 0;
102 } while (flip (&d
) != LEFT
);
104 *equally_long
= false;
105 if (!(beams
[LEFT
] && (beams
[LEFT
] == beams
[RIGHT
]) && !me
->is_broken ()))
108 extract_grob_set (beams
[LEFT
], "stems", beam_stems
);
109 if (beam_stems
.size () == 0)
111 programming_error ("beam under tuplet bracket has no stems");
117 (beam_stems
[0] == stems
[LEFT
]
118 && beam_stems
.back () == stems
[RIGHT
]);
123 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_connect_to_neighbors
, 1);
125 Tuplet_bracket::calc_connect_to_neighbors (SCM smob
)
127 Spanner
*me
= unsmob_spanner (smob
);
129 Direction dir
= get_grob_direction (me
);
130 Drul_array
<Item
*> bounds (get_x_bound_item (me
, LEFT
, dir
),
131 get_x_bound_item (me
, RIGHT
, dir
));
133 Drul_array
<bool> connect_to_other (false, false);
137 Direction break_dir
= bounds
[d
]->break_status_dir ();
138 Spanner
*orig_spanner
= dynamic_cast<Spanner
*> (me
->original ());
139 vsize neighbor_idx
= me
->get_break_index () - break_dir
;
142 && neighbor_idx
< orig_spanner
->broken_intos_
.size ())
144 Grob
*neighbor
= orig_spanner
->broken_intos_
[neighbor_idx
];
146 /* trigger possible suicide*/
147 (void) neighbor
->get_property ("positions");
152 && neighbor_idx
< orig_spanner
->broken_intos_
.size ()
153 && orig_spanner
->broken_intos_
[neighbor_idx
]->is_live ());
155 while (flip (&d
) != LEFT
);
158 if (connect_to_other
[LEFT
] || connect_to_other
[RIGHT
])
159 return scm_cons (scm_from_bool (connect_to_other
[LEFT
]),
160 scm_from_bool (connect_to_other
[RIGHT
]));
166 Tuplet_bracket::get_common_x (Spanner
*me
)
168 extract_grob_set (me
, "note-columns", columns
);
170 Grob
*commonx
= common_refpoint_of_array (columns
, me
, X_AXIS
);
171 commonx
= commonx
->common_refpoint (me
->get_bound (LEFT
), X_AXIS
);
172 commonx
= commonx
->common_refpoint (me
->get_bound (RIGHT
), X_AXIS
);
177 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_control_points
, 1)
179 Tuplet_bracket::calc_control_points (SCM smob
)
181 Spanner
*me
= unsmob_spanner (smob
);
183 extract_grob_set (me
, "note-columns", columns
);
185 SCM scm_positions
= me
->get_property ("positions");
189 if (!scm_is_pair (scm_positions
))
190 programming_error ("Positions should be number pair");
192 Drul_array
<Real
> positions
193 = robust_scm2drul (scm_positions
, Drul_array
<Real
> (0, 0));
195 Grob
*commonx
= get_common_x (me
);
196 Direction dir
= get_grob_direction (me
);
198 Drul_array
<Item
*> bounds
;
199 bounds
[LEFT
] = get_x_bound_item (me
, LEFT
, dir
);
200 bounds
[RIGHT
] = get_x_bound_item (me
, RIGHT
, dir
);
202 Drul_array
<bool> connect_to_other
=
203 robust_scm2booldrul (me
->get_property ("connect-to-neighbor"),
204 Drul_array
<bool> (false, false));
210 x_span
[d
] = robust_relative_extent (bounds
[d
], commonx
, X_AXIS
)[d
];
212 if (connect_to_other
[d
])
214 Interval
overshoot (robust_scm2drul (me
->get_property ("break-overshoot"),
215 Interval (-0.5, 0.0)));
218 x_span
[d
] += d
* overshoot
[d
];
220 x_span
[d
] = robust_relative_extent (bounds
[d
],
221 commonx
, X_AXIS
)[RIGHT
]
227 || (bounds
[d
]->get_column ()
228 != dynamic_cast<Item
*> (columns
.back ())->get_column ())))
231 We're connecting to a column, for the last bit of a broken
235 robust_scm2double(me
->get_property("full-length-padding"), 1.0);
237 if (bounds
[d
]->break_status_dir ())
240 Real coord
= bounds
[d
]->relative_coordinate(commonx
, X_AXIS
);
241 if (to_boolean (me
->get_property ("full-length-to-extent")))
242 coord
= robust_relative_extent(bounds
[d
], commonx
, X_AXIS
)[LEFT
];
244 coord
= max (coord
, x_span
[LEFT
]);
246 x_span
[d
] = coord
- padding
;
249 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
;
285 Don't print a tuplet bracket and number if
286 no control-points were calculated
288 SCM cpoints
= me
->get_property ("control-points");
289 if (scm_ilength (cpoints
) < 2)
294 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
295 the bracket, but still let the number be displayed */
296 if (robust_scm2moment (me
->get_bound (LEFT
)->get_column ()->get_property ("when"), Moment (0))
297 == robust_scm2moment (me
->get_bound (RIGHT
)->get_column ()->get_property ("when"), Moment (0)))
299 bracket_visibility
= false;
302 Drul_array
<Offset
> points
;
303 points
[LEFT
] = ly_scm2offset (scm_car (cpoints
));
304 points
[RIGHT
] = ly_scm2offset (scm_cadr (cpoints
));
306 Interval
x_span (points
[LEFT
][X_AXIS
], points
[RIGHT
][X_AXIS
]);
307 Drul_array
<Real
> positions (points
[LEFT
][Y_AXIS
], points
[RIGHT
][Y_AXIS
]);
309 Output_def
*pap
= me
->layout ();
311 Grob
*number_grob
= unsmob_grob (me
->get_object ("tuplet-number"));
314 No bracket when it would be smaller than the number.
317 if (bracket_visibility
&& number_grob
)
319 Interval ext
= number_grob
->extent (number_grob
, X_AXIS
);
320 if (!ext
.is_empty ())
322 gap
= ext
.length () + 1.0;
324 if (0.75 * x_span
.length () < gap
)
325 bracket_visibility
= false;
329 if (bracket_visibility
)
331 Drul_array
<Real
> zero (0, 0);
332 Real ss
= Staff_symbol_referencer::staff_space (me
);
333 Drul_array
<Real
> height
334 = robust_scm2drul (me
->get_property ("edge-height"), zero
);
335 Drul_array
<Real
> flare
336 = robust_scm2drul (me
->get_property ("bracket-flare"), zero
);
337 Drul_array
<Real
> shorten
338 = robust_scm2drul (me
->get_property ("shorten-pair"), zero
);
339 Drul_array
<Stencil
> edge_stencils
;
341 Direction dir
= get_grob_direction (me
);
343 scale_drul (&height
, -ss
* dir
);
344 scale_drul (&flare
, ss
);
345 scale_drul (&shorten
, ss
);
347 Drul_array
<bool> connect_to_other
=
348 robust_scm2booldrul (me
->get_property ("connect-to-neighbor"),
349 Drul_array
<bool> (false, false));
354 if (connect_to_other
[d
])
360 SCM edge_text
= me
->get_property ("edge-text");
362 if (scm_is_pair (edge_text
))
364 SCM properties
= Font_interface::text_font_alist_chain (me
);
365 SCM text
= index_get_cell (edge_text
, d
);
366 if (Text_interface::is_markup (text
))
369 = Text_interface::interpret_markup (pap
->self_scm (),
372 Stencil
*edge_text
= unsmob_stencil (t
);
373 edge_text
->translate_axis (x_span
[d
] - x_span
[LEFT
],
375 edge_stencils
[d
] = *edge_text
;
380 while (flip (&d
) != LEFT
);
382 Stencil brack
= make_bracket (me
, Y_AXIS
,
383 points
[RIGHT
] - points
[LEFT
],
386 0.1 = more space at right due to italics
387 TODO: use italic correction of font.
389 Interval (-0.5, 0.5) * gap
+ 0.1,
394 if (!edge_stencils
[d
].is_empty ())
395 brack
.add_stencil (edge_stencils
[d
]);
397 while (flip (&d
) != LEFT
);
399 mol
.add_stencil (brack
);
402 mol
.translate (points
[LEFT
]);
403 return mol
.smobbed_copy ();
407 should move to lookup?
409 TODO: this will fail for very short (shorter than the flare)
413 Tuplet_bracket::make_bracket (Grob
*me
, // for line properties.
414 Axis protrusion_axis
,
416 Drul_array
<Real
> height
,
418 Drul_array
<Real
> flare
,
419 Drul_array
<Real
> shorten
)
421 Drul_array
<Offset
> corners (Offset (0, 0), dz
);
423 Real length
= dz
.length ();
424 Drul_array
<Offset
> gap_corners
;
426 Axis bracket_axis
= other_axis (protrusion_axis
);
428 Drul_array
<Offset
> straight_corners
= corners
;
432 straight_corners
[d
] += -d
* shorten
[d
] / length
* dz
;
433 while (flip (&d
) != LEFT
);
435 if (!gap
.is_empty ())
438 gap_corners
[d
] = (dz
* 0.5) + gap
[d
] / length
* dz
;
439 while (flip (&d
) != LEFT
);
442 Drul_array
<Offset
> flare_corners
= straight_corners
;
445 flare_corners
[d
][bracket_axis
] = straight_corners
[d
][bracket_axis
];
446 flare_corners
[d
][protrusion_axis
] += height
[d
];
447 straight_corners
[d
][bracket_axis
] += -d
* flare
[d
];
449 while (flip (&d
) != LEFT
);
454 if (!gap
.is_empty ())
455 m
.add_stencil (Line_interface::line (me
, straight_corners
[d
],
458 m
.add_stencil (Line_interface::line (me
, straight_corners
[d
],
462 while (flip (&d
) != LEFT
);
465 m
.add_stencil (Line_interface::line (me
, straight_corners
[LEFT
],
466 straight_corners
[RIGHT
]));
472 Tuplet_bracket::get_bounds (Grob
*me
, Grob
**left
, Grob
**right
)
474 extract_grob_set (me
, "note-columns", columns
);
476 while (l
< columns
.size () && Note_column::has_rests (columns
[l
]))
479 vsize r
= columns
.size ();
480 while (r
> l
&& Note_column::has_rests (columns
[r
-1]))
488 *right
= columns
[r
-1];
493 use first -> last note for slope, and then correct for disturbing
496 Tuplet_bracket::calc_position_and_height (Grob
*me_grob
, Real
*offset
, Real
*dy
)
498 Spanner
*me
= dynamic_cast<Spanner
*> (me_grob
);
500 extract_grob_set (me
, "note-columns", columns
);
501 extract_grob_set (me
, "tuplets", tuplets
);
503 Grob
*commony
= common_refpoint_of_array (columns
, me
, Y_AXIS
);
504 commony
= common_refpoint_of_array (tuplets
, commony
, Y_AXIS
);
505 if (Grob
*st
= Staff_symbol_referencer::get_staff_symbol (me
))
506 commony
= st
->common_refpoint (commony
, Y_AXIS
);
507 Real my_offset
= me
->relative_coordinate (commony
, Y_AXIS
);
509 Grob
*commonx
= get_common_x (me
);
510 commonx
= common_refpoint_of_array (tuplets
, commonx
, Y_AXIS
);
513 Grob
*st
= Staff_symbol_referencer::get_staff_symbol (me
);
515 /* staff-padding doesn't work correctly on cross-staff tuplets
516 because it only considers one staff symbol. Until this works,
518 if (st
&& !to_boolean (me
->get_property ("cross-staff")))
520 Real pad
= robust_scm2double (me
->get_property ("staff-padding"), -1.0);
523 staff
= st
->extent (commony
, Y_AXIS
) - my_offset
;
528 Direction dir
= get_grob_direction (me
);
530 bool equally_long
= false;
531 Grob
*par_beam
= parallel_beam (me
, columns
, &equally_long
);
533 Item
*lgr
= get_x_bound_item (me
, LEFT
, dir
);
534 Item
*rgr
= get_x_bound_item (me
, RIGHT
, dir
);
535 Real x0
= robust_relative_extent (lgr
, commonx
, X_AXIS
)[LEFT
];
536 Real x1
= robust_relative_extent (rgr
, commonx
, X_AXIS
)[RIGHT
];
537 bool follow_beam
= par_beam
538 && get_grob_direction (par_beam
) == dir
539 && ! to_boolean (par_beam
->get_property ("knee"));
541 vector
<Offset
> points
;
544 && Note_column::get_stem (columns
[0])
545 && Note_column::get_stem (columns
.back ()))
548 trigger set_stem_ends
550 (void) par_beam
->get_property ("quantized-positions");
552 Drul_array
<Grob
*> stems (Note_column::get_stem (columns
[0]),
553 Note_column::get_stem (columns
.back ()));
555 Real ss
= 0.5 * Staff_symbol_referencer::staff_space (me
);
556 Real lp
= ss
* robust_scm2double (stems
[LEFT
]->get_property ("stem-end-position"), 0.0)
557 + stems
[LEFT
]->get_parent (Y_AXIS
)->relative_coordinate (commony
, Y_AXIS
);
558 Real rp
= ss
* robust_scm2double (stems
[RIGHT
]->get_property ("stem-end-position"), 0.0)
559 + stems
[RIGHT
]->get_parent (Y_AXIS
)->relative_coordinate (commony
, Y_AXIS
);
562 points
.push_back (Offset (stems
[LEFT
]->relative_coordinate (commonx
, X_AXIS
) - x0
, lp
));
563 points
.push_back (Offset (stems
[RIGHT
]->relative_coordinate (commonx
, X_AXIS
) - x0
, rp
));
568 Use outer non-rest columns to determine slope
572 get_bounds (me
, &left_col
, &right_col
);
573 if (left_col
&& right_col
)
575 Interval rv
= Note_column::cross_staff_extent (right_col
, commony
);
576 Interval lv
= Note_column::cross_staff_extent (left_col
, commony
);
580 Real graphical_dy
= rv
[dir
] - lv
[dir
];
582 Slice ls
= Note_column::head_positions_interval (left_col
);
583 Slice rs
= Note_column::head_positions_interval (right_col
);
586 musical_dy
[UP
] = rs
[UP
] - ls
[UP
];
587 musical_dy
[DOWN
] = rs
[DOWN
] - ls
[DOWN
];
588 if (sign (musical_dy
[UP
]) != sign (musical_dy
[DOWN
]))
590 else if (sign (graphical_dy
) != sign (musical_dy
[DOWN
]))
598 for (vsize i
= 0; i
< columns
.size (); i
++)
600 Interval note_ext
= Note_column::cross_staff_extent (columns
[i
],
602 Real x
= columns
[i
]->relative_coordinate (commonx
, X_AXIS
) - x0
;
604 points
.push_back (Offset (x
, note_ext
[dir
]));
610 points
.push_back (Offset (x0
- x0
, staff
[dir
]));
611 points
.push_back (Offset (x1
- x0
, staff
[dir
]));
615 This is a slight hack. We compute two encompass points from the
616 bbox of the smaller tuplets.
618 We assume that the smaller bracket is 1.0 space high.
620 Real ss
= Staff_symbol_referencer::staff_space (me
);
621 for (vsize i
= 0; i
< tuplets
.size (); i
++)
623 Interval
tuplet_x (tuplets
[i
]->extent (commonx
, X_AXIS
));
624 Interval
tuplet_y (tuplets
[i
]->extent (commony
, Y_AXIS
));
626 if (!tuplets
[i
]->is_live ())
630 Drul_array
<Real
> positions
631 = robust_scm2interval (tuplets
[i
]->get_property ("positions"),
634 Real other_dy
= positions
[RIGHT
] - positions
[LEFT
];
639 = tuplet_y
.linear_combination (d
* sign (other_dy
));
642 We don't take padding into account for nested tuplets.
643 the edges can come very close to the stems, likewise for
647 points
.push_back (Offset (tuplet_x
[d
] - x0
, y
));
649 while (flip (&d
) != LEFT
);
652 *offset
= -dir
* infinity_f
;
653 Real factor
= (columns
.size () > 1) ? 1 / (x1
- x0
) : 1.0;
654 for (vsize i
= 0; i
< points
.size (); i
++)
656 Real x
= points
[i
][X_AXIS
];
657 Real tuplety
= (*dy
) * x
* factor
+ my_offset
;
659 if (points
[i
][Y_AXIS
] * dir
> (*offset
+ tuplety
) * dir
)
660 *offset
= points
[i
][Y_AXIS
] - tuplety
;
663 *offset
+= scm_to_double (me
->get_property ("padding")) * dir
;
666 horizontal brackets should not collide with staff lines.
668 Kind of pointless since we put them outside the staff anyway, but
669 let's leave code for the future when possibly allow them to move
670 into the staff once again.
672 This doesn't seem to support cross-staff tuplets atm.
675 && fabs (*offset
) < ss
* Staff_symbol_referencer::staff_radius (me
))
677 // quantize, then do collision check.
680 *offset
= rint (*offset
);
681 if (Staff_symbol_referencer::on_line (me
, (int) rint (*offset
)))
688 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_direction
, 1);
690 Tuplet_bracket::calc_direction (SCM smob
)
692 Grob
*me
= unsmob_grob (smob
);
693 Direction dir
= Tuplet_bracket::get_default_dir (me
);
694 return scm_from_int (dir
);
697 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_positions
, 1);
699 Tuplet_bracket::calc_positions (SCM smob
)
701 Spanner
*me
= unsmob_spanner (smob
);
705 calc_position_and_height (me
, &offset
, &dy
);
707 SCM x
= scm_cons (scm_from_double (offset
),
708 scm_from_double (offset
+ dy
));
717 Tuplet_bracket::get_default_dir (Grob
*me
)
719 Drul_array
<int> dirs (0, 0);
720 extract_grob_set (me
, "note-columns", columns
);
721 for (vsize i
= 0; i
< columns
.size (); i
++)
723 Grob
*nc
= columns
[i
];
724 Direction d
= Note_column::dir (nc
);
729 return dirs
[UP
] >= dirs
[DOWN
] ? UP
: DOWN
;
733 Tuplet_bracket::add_column (Grob
*me
, Item
*n
)
735 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("note-columns"), n
);
736 add_bound_item (dynamic_cast<Spanner
*> (me
), n
);
740 Tuplet_bracket::add_tuplet_bracket (Grob
*me
, Grob
*bracket
)
742 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("tuplets"), bracket
);
745 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_cross_staff
, 1);
747 Tuplet_bracket::calc_cross_staff (SCM smob
)
749 Grob
*me
= unsmob_grob (smob
);
750 extract_grob_set (me
, "note-columns", cols
);
751 extract_grob_set (me
, "tuplets", tuplets
);
753 Grob
*commony
= common_refpoint_of_array (cols
, me
, Y_AXIS
);
754 commony
= common_refpoint_of_array (tuplets
, commony
, Y_AXIS
);
755 if (Grob
*st
= Staff_symbol_referencer::get_staff_symbol (me
))
756 commony
= st
->common_refpoint (commony
, Y_AXIS
);
757 if (me
->check_cross_staff (commony
))
760 bool equally_long
= false;
761 Grob
*par_beam
= parallel_beam (me
, cols
, &equally_long
);
763 if (par_beam
&& to_boolean (par_beam
->get_property ("cross-staff")))
766 for (vsize i
= 0; i
< cols
.size (); i
++)
768 Grob
*stem
= unsmob_grob (cols
[i
]->get_object ("stem"));
769 if (stem
&& to_boolean (stem
->get_property ("cross-staff")))
776 ADD_INTERFACE (Tuplet_bracket
,
777 "A bracket with a number in the middle, used for tuplets."
778 " When the bracket spans a line break, the value of"
779 " @code{break-overshoot} determines how far it extends"
780 " beyond the staff. At a line break, the markups in the"
781 " @code{edge-text} are printed at the edges.",
785 "bracket-visibility "
787 "connect-to-neighbor "
792 "full-length-padding "
793 "full-length-to-extent "