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
,
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.
81 Tuplet_bracket::parallel_beam (Grob
*me_grob
, vector
<Grob
*> const &cols
,
84 Spanner
*me
= dynamic_cast<Spanner
*> (me_grob
);
86 if (me
->get_bound (LEFT
)->break_status_dir ()
87 || me
->get_bound (RIGHT
)->break_status_dir ())
90 Drul_array
<Grob
*> stems (Note_column::get_stem (cols
[0]),
91 Note_column::get_stem (cols
.back ()));
95 || (dynamic_cast<Item
*> (stems
[RIGHT
])->get_column ()
96 != me
->get_bound (RIGHT
)->get_column ()))
99 Drul_array
<Grob
*> beams
;
102 beams
[d
] = stems
[d
] ? Stem::get_beam (stems
[d
]) : 0;
103 } while (flip (&d
) != LEFT
);
105 *equally_long
= false;
106 if (! (beams
[LEFT
] && (beams
[LEFT
] == beams
[RIGHT
]) && !me
->is_broken ()))
109 extract_grob_set (beams
[LEFT
], "stems", beam_stems
);
110 if (beam_stems
.size () == 0)
112 programming_error ("beam under tuplet bracket has no stems");
118 (beam_stems
[0] == stems
[LEFT
]
119 && beam_stems
.back () == stems
[RIGHT
]);
124 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_connect_to_neighbors
,1);
126 Tuplet_bracket::calc_connect_to_neighbors (SCM smob
)
128 Spanner
*me
= unsmob_spanner (smob
);
130 Direction dir
= get_grob_direction (me
);
131 Drul_array
<Item
*> bounds (get_x_bound_item (me
, LEFT
, dir
),
132 get_x_bound_item (me
, RIGHT
, dir
));
134 Drul_array
<bool> connect_to_other (false, false);
138 Direction break_dir
= bounds
[d
]->break_status_dir ();
139 Spanner
*orig_spanner
= dynamic_cast<Spanner
*> (me
->original ());
140 vsize neighbor_idx
= me
->get_break_index () - break_dir
;
143 && neighbor_idx
< orig_spanner
->broken_intos_
.size ())
145 Grob
*neighbor
= orig_spanner
->broken_intos_
[neighbor_idx
];
147 /* trigger possible suicide*/
148 (void) neighbor
->get_property ("positions");
153 && neighbor_idx
< orig_spanner
->broken_intos_
.size ()
154 && orig_spanner
->broken_intos_
[neighbor_idx
]->is_live ());
156 while (flip (&d
) != LEFT
);
159 if (connect_to_other
[LEFT
] || connect_to_other
[RIGHT
])
160 return scm_cons (scm_from_bool (connect_to_other
[LEFT
]),
161 scm_from_bool (connect_to_other
[RIGHT
]));
167 Tuplet_bracket::get_common_x (Spanner
*me
)
169 extract_grob_set (me
, "note-columns", columns
);
171 Grob
* commonx
= common_refpoint_of_array (columns
, me
, X_AXIS
);
172 commonx
= commonx
->common_refpoint (me
->get_bound (LEFT
), X_AXIS
);
173 commonx
= commonx
->common_refpoint (me
->get_bound (RIGHT
), X_AXIS
);
178 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_control_points
,1)
180 Tuplet_bracket::calc_control_points (SCM smob
)
182 Spanner
*me
= unsmob_spanner (smob
);
184 extract_grob_set (me
, "note-columns", columns
);
186 SCM scm_positions
= me
->get_property ("positions");
190 if (!scm_is_pair (scm_positions
))
191 programming_error ("Positions should be number pair");
193 Drul_array
<Real
> positions
194 = robust_scm2drul (scm_positions
, Drul_array
<Real
> (0,0));
196 Grob
*commonx
= get_common_x (me
);
197 Direction dir
= get_grob_direction (me
);
199 Drul_array
<Item
*> bounds
;
200 bounds
[LEFT
] = get_x_bound_item (me
, LEFT
, dir
);
201 bounds
[RIGHT
] = get_x_bound_item (me
, RIGHT
, dir
);
203 Drul_array
<bool> connect_to_other
=
204 robust_scm2booldrul (me
->get_property ("connect-to-neighbor"),
205 Drul_array
<bool> (false, false));
212 x_span
[d
] = robust_relative_extent (bounds
[d
], commonx
, X_AXIS
)[d
];
214 if (connect_to_other
[d
])
216 Interval
overshoot (robust_scm2drul (me
->get_property ("break-overshoot"),
217 Interval (-0.5, 0.0)));
220 x_span
[d
] += d
* overshoot
[d
];
222 x_span
[d
] = robust_relative_extent (bounds
[d
], commonx
, X_AXIS
)[RIGHT
]
228 || (bounds
[d
]->get_column ()
229 != dynamic_cast<Item
*> (columns
.back ())->get_column ())))
232 We're connecting to a column, for the last bit of a broken
236 robust_scm2double(me
->get_property("full-length-padding"), 1.0);
238 if (bounds
[d
]->break_status_dir ())
241 Real coord
= bounds
[d
]->relative_coordinate(commonx
, X_AXIS
);
242 if (to_boolean (me
->get_property ("full-length-to-extent")))
243 coord
= robust_relative_extent(bounds
[d
], commonx
, X_AXIS
)[LEFT
];
245 coord
= max (coord
, x_span
[LEFT
]);
247 x_span
[d
] = coord
- padding
;
250 while (flip (&d
) != LEFT
);
254 x_span
-= me
->get_bound (LEFT
)->relative_coordinate (commonx
, X_AXIS
);
255 return scm_list_2 (ly_offset2scm (Offset (x_span
[LEFT
], positions
[LEFT
])),
256 ly_offset2scm (Offset (x_span
[RIGHT
], positions
[RIGHT
])));
262 in the case that there is no bracket, but there is a (single) beam,
263 follow beam precisely for determining tuplet number location.
265 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, print
, 1);
267 Tuplet_bracket::print (SCM smob
)
269 Spanner
*me
= unsmob_spanner (smob
);
272 extract_grob_set (me
, "note-columns", columns
);
273 bool equally_long
= false;
274 Grob
*par_beam
= parallel_beam (me
, columns
, &equally_long
);
276 bool bracket_visibility
= !(par_beam
&& equally_long
);
278 Fixme: the type of this prop is sucky.
280 SCM bracket
= me
->get_property ("bracket-visibility");
281 if (scm_is_bool (bracket
))
282 bracket_visibility
= ly_scm2bool (bracket
);
283 else if (bracket
== ly_symbol2scm ("if-no-beam"))
284 bracket_visibility
= !par_beam
;
286 /* Don't print a tuplet bracket and number if no control-points were calculated */
287 SCM cpoints
= me
->get_property ("control-points");
288 if (scm_ilength (cpoints
) < 2)
293 /* if the tuplet does not span any time, i.e. a single-note tuplet, hide
294 the bracket, but still let the number be displayed */
295 if (robust_scm2moment (me
->get_bound (LEFT
)->get_column ()->get_property ("when"), Moment (0))
296 == robust_scm2moment (me
->get_bound (RIGHT
)->get_column ()->get_property ("when"), Moment (0)))
298 bracket_visibility
= false;
301 Drul_array
<Offset
> points
;
302 points
[LEFT
] = ly_scm2offset (scm_car (cpoints
));
303 points
[RIGHT
] = ly_scm2offset (scm_cadr (cpoints
));
305 Interval
x_span (points
[LEFT
][X_AXIS
], points
[RIGHT
][X_AXIS
]);
306 Drul_array
<Real
> positions (points
[LEFT
][Y_AXIS
], points
[RIGHT
][Y_AXIS
]);
308 Output_def
*pap
= me
->layout ();
310 Grob
*number_grob
= unsmob_grob (me
->get_object ("tuplet-number"));
313 No bracket when it would be smaller than the number.
316 if (bracket_visibility
&& number_grob
)
318 Interval ext
= number_grob
->extent (number_grob
, X_AXIS
);
319 if (!ext
.is_empty ())
321 gap
= ext
.length () + 1.0;
323 if (0.75 * x_span
.length () < gap
)
324 bracket_visibility
= false;
328 if (bracket_visibility
)
330 Drul_array
<Real
> zero (0, 0);
331 Real ss
= Staff_symbol_referencer::staff_space (me
);
332 Drul_array
<Real
> height
333 = robust_scm2drul (me
->get_property ("edge-height"), zero
);
334 Drul_array
<Real
> flare
335 = robust_scm2drul (me
->get_property ("bracket-flare"), zero
);
336 Drul_array
<Real
> shorten
337 = robust_scm2drul (me
->get_property ("shorten-pair"), zero
);
338 Drul_array
<Stencil
> edge_stencils
;
340 Direction dir
= get_grob_direction (me
);
342 scale_drul (&height
, -ss
* dir
);
343 scale_drul (&flare
, ss
);
344 scale_drul (&shorten
, ss
);
346 Drul_array
<bool> connect_to_other
=
347 robust_scm2booldrul (me
->get_property ("connect-to-neighbor"),
348 Drul_array
<bool> (false, false));
353 if (connect_to_other
[d
])
359 SCM edge_text
= me
->get_property ("edge-text");
361 if (scm_is_pair (edge_text
))
363 SCM properties
= Font_interface::text_font_alist_chain (me
);
364 SCM text
= index_get_cell (edge_text
, d
);
365 if (Text_interface::is_markup (text
))
367 SCM t
= Text_interface::interpret_markup (pap
->self_scm (),
370 Stencil
*edge_text
= unsmob_stencil (t
);
371 edge_text
->translate_axis (x_span
[d
] - x_span
[LEFT
], X_AXIS
);
372 edge_stencils
[d
] = *edge_text
;
377 while (flip (&d
) != LEFT
);
379 Stencil brack
= make_bracket (me
, Y_AXIS
,
380 points
[RIGHT
] - points
[LEFT
],
383 0.1 = more space at right due to italics
384 TODO: use italic correction of font.
386 Interval (-0.5, 0.5) * gap
+ 0.1,
391 if (!edge_stencils
[d
].is_empty ())
392 brack
.add_stencil (edge_stencils
[d
]);
394 while (flip (&d
) != LEFT
);
396 mol
.add_stencil (brack
);
399 mol
.translate (points
[LEFT
]);
400 return mol
.smobbed_copy ();
404 should move to lookup?
406 TODO: this will fail for very short (shorter than the flare)
410 Tuplet_bracket::make_bracket (Grob
*me
, // for line properties.
413 Drul_array
<Real
> height
,
415 Drul_array
<Real
> flare
,
416 Drul_array
<Real
> shorten
)
418 Drul_array
<Offset
> corners (Offset (0, 0), dz
);
420 Real length
= dz
.length ();
421 Drul_array
<Offset
> gap_corners
;
423 Axis bracket_axis
= other_axis (protusion_axis
);
425 Drul_array
<Offset
> straight_corners
= corners
;
429 straight_corners
[d
] += -d
* shorten
[d
] / length
* dz
;
430 while (flip (&d
) != LEFT
);
432 if (!gap
.is_empty ())
435 gap_corners
[d
] = (dz
* 0.5) + gap
[d
] / length
* dz
;
436 while (flip (&d
) != LEFT
);
439 Drul_array
<Offset
> flare_corners
= straight_corners
;
442 flare_corners
[d
][bracket_axis
] = straight_corners
[d
][bracket_axis
];
443 flare_corners
[d
][protusion_axis
] += height
[d
];
444 straight_corners
[d
][bracket_axis
] += -d
* flare
[d
];
446 while (flip (&d
) != LEFT
);
451 if (!gap
.is_empty ())
452 m
.add_stencil (Line_interface::line (me
, straight_corners
[d
],
455 m
.add_stencil (Line_interface::line (me
, straight_corners
[d
],
459 while (flip (&d
) != LEFT
);
462 m
.add_stencil (Line_interface::line (me
, straight_corners
[LEFT
],
463 straight_corners
[RIGHT
]));
469 Tuplet_bracket::get_bounds (Grob
*me
, Grob
**left
, Grob
**right
)
471 extract_grob_set (me
, "note-columns", columns
);
473 while (l
< columns
.size () && Note_column::has_rests (columns
[l
]))
476 vsize r
= columns
.size ();
477 while (r
> l
&& Note_column::has_rests (columns
[r
-1]))
485 *right
= columns
[r
-1];
490 use first -> last note for slope, and then correct for disturbing
493 Tuplet_bracket::calc_position_and_height (Grob
*me_grob
, Real
*offset
, Real
*dy
)
495 Spanner
*me
= dynamic_cast<Spanner
*> (me_grob
);
497 extract_grob_set (me
, "note-columns", columns
);
498 extract_grob_set (me
, "tuplets", tuplets
);
500 Grob
*commony
= common_refpoint_of_array (columns
, me
, Y_AXIS
);
501 commony
= common_refpoint_of_array (tuplets
, commony
, Y_AXIS
);
502 if (Grob
*st
= Staff_symbol_referencer::get_staff_symbol (me
))
503 commony
= st
->common_refpoint (commony
, Y_AXIS
);
504 Real my_offset
= me
->relative_coordinate (commony
, Y_AXIS
);
506 Grob
*commonx
= get_common_x (me
);
507 commonx
= common_refpoint_of_array (tuplets
, commonx
, Y_AXIS
);
510 Grob
*st
= Staff_symbol_referencer::get_staff_symbol (me
);
512 /* staff-padding doesn't work correctly on cross-staff tuplets
513 because it only considers one staff symbol. Until this works,
515 if (st
&& !to_boolean (me
->get_property ("cross-staff")))
517 Real pad
= robust_scm2double (me
->get_property ("staff-padding"), -1.0);
520 staff
= st
->extent (commony
, Y_AXIS
) - my_offset
;
525 Direction dir
= get_grob_direction (me
);
527 bool equally_long
= false;
528 Grob
*par_beam
= parallel_beam (me
, columns
, &equally_long
);
530 Item
*lgr
= get_x_bound_item (me
, LEFT
, dir
);
531 Item
*rgr
= get_x_bound_item (me
, RIGHT
, dir
);
532 Real x0
= robust_relative_extent (lgr
, commonx
, X_AXIS
)[LEFT
];
533 Real x1
= robust_relative_extent (rgr
, commonx
, X_AXIS
)[RIGHT
];
534 bool follow_beam
= par_beam
535 && get_grob_direction (par_beam
) == dir
536 && ! to_boolean (par_beam
->get_property ("knee"));
538 vector
<Offset
> points
;
541 && Note_column::get_stem (columns
[0])
542 && Note_column::get_stem (columns
.back ()))
545 trigger set_stem_ends
547 (void) par_beam
->get_property ("quantized-positions");
549 Drul_array
<Grob
*> stems (Note_column::get_stem (columns
[0]),
550 Note_column::get_stem (columns
.back ()));
552 Real ss
= 0.5 * Staff_symbol_referencer::staff_space (me
);
553 Real lp
= ss
* robust_scm2double (stems
[LEFT
]->get_property ("stem-end-position"), 0.0)
554 + stems
[LEFT
]->get_parent (Y_AXIS
)->relative_coordinate (commony
, Y_AXIS
);
555 Real rp
= ss
* robust_scm2double (stems
[RIGHT
]->get_property ("stem-end-position"), 0.0)
556 + stems
[RIGHT
]->get_parent (Y_AXIS
)->relative_coordinate (commony
, Y_AXIS
);
559 points
.push_back (Offset (stems
[LEFT
]->relative_coordinate (commonx
, X_AXIS
) - x0
, lp
));
560 points
.push_back (Offset (stems
[RIGHT
]->relative_coordinate (commonx
, X_AXIS
) - x0
, rp
));
565 Use outer non-rest columns to determine slope
569 get_bounds (me
, &left_col
, &right_col
);
570 if (left_col
&& right_col
)
572 Interval rv
= Note_column::cross_staff_extent (right_col
, commony
);
573 Interval lv
= Note_column::cross_staff_extent (left_col
, commony
);
577 Real graphical_dy
= rv
[dir
] - lv
[dir
];
579 Slice ls
= Note_column::head_positions_interval (left_col
);
580 Slice rs
= Note_column::head_positions_interval (right_col
);
583 musical_dy
[UP
] = rs
[UP
] - ls
[UP
];
584 musical_dy
[DOWN
] = rs
[DOWN
] - ls
[DOWN
];
585 if (sign (musical_dy
[UP
]) != sign (musical_dy
[DOWN
]))
587 else if (sign (graphical_dy
) != sign (musical_dy
[DOWN
]))
595 for (vsize i
= 0; i
< columns
.size (); i
++)
597 Interval note_ext
= Note_column::cross_staff_extent (columns
[i
], commony
);
598 Real x
= columns
[i
]->relative_coordinate (commonx
, X_AXIS
) - x0
;
600 points
.push_back (Offset (x
, note_ext
[dir
]));
606 points
.push_back (Offset (x0
- x0
, staff
[dir
]));
607 points
.push_back (Offset (x1
- x0
, staff
[dir
]));
611 This is a slight hack. We compute two encompass points from the
612 bbox of the smaller tuplets.
614 We assume that the smaller bracket is 1.0 space high.
616 Real ss
= Staff_symbol_referencer::staff_space (me
);
617 for (vsize i
= 0; i
< tuplets
.size (); i
++)
619 Interval
tuplet_x (tuplets
[i
]->extent (commonx
, X_AXIS
));
620 Interval
tuplet_y (tuplets
[i
]->extent (commony
, Y_AXIS
));
622 if (!tuplets
[i
]->is_live ())
626 Drul_array
<Real
> positions
= robust_scm2interval (tuplets
[i
]->get_property ("positions"),
630 Real other_dy
= positions
[RIGHT
] - positions
[LEFT
];
635 = tuplet_y
.linear_combination (d
* sign (other_dy
));
638 We don't take padding into account for nested tuplets.
639 the edges can come very close to the stems, likewise for
643 points
.push_back (Offset (tuplet_x
[d
] - x0
, y
));
645 while (flip (&d
) != LEFT
);
648 *offset
= -dir
* infinity_f
;
649 Real factor
= (columns
.size () > 1) ? 1 / (x1
- x0
) : 1.0;
650 for (vsize i
= 0; i
< points
.size (); i
++)
652 Real x
= points
[i
][X_AXIS
];
653 Real tuplety
= (*dy
) * x
* factor
+ my_offset
;
655 if (points
[i
][Y_AXIS
] * dir
> (*offset
+ tuplety
) * dir
)
656 *offset
= points
[i
][Y_AXIS
] - tuplety
;
659 *offset
+= scm_to_double (me
->get_property ("padding")) * dir
;
662 horizontal brackets should not collide with staff lines.
664 Kind of pointless since we put them outside the staff anyway, but
665 let's leave code for the future when possibly allow them to move
666 into the staff once again.
668 This doesn't seem to support cross-staff tuplets atm.
671 && fabs (*offset
) < ss
* Staff_symbol_referencer::staff_radius (me
))
673 // quantize, then do collision check.
676 *offset
= rint (*offset
);
677 if (Staff_symbol_referencer::on_line (me
, (int) rint (*offset
)))
685 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_direction
, 1);
687 Tuplet_bracket::calc_direction (SCM smob
)
689 Grob
*me
= unsmob_grob (smob
);
690 Direction dir
= Tuplet_bracket::get_default_dir (me
);
691 return scm_from_int (dir
);
694 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_positions
, 1);
696 Tuplet_bracket::calc_positions (SCM smob
)
698 Spanner
*me
= unsmob_spanner (smob
);
702 calc_position_and_height (me
, &offset
, &dy
);
704 SCM x
= scm_cons (scm_from_double (offset
),
705 scm_from_double (offset
+ dy
));
714 Tuplet_bracket::get_default_dir (Grob
*me
)
716 Drul_array
<int> dirs (0, 0);
717 extract_grob_set (me
, "note-columns", columns
);
718 for (vsize i
= 0; i
< columns
.size (); i
++)
720 Grob
*nc
= columns
[i
];
721 Direction d
= Note_column::dir (nc
);
726 return dirs
[UP
] >= dirs
[DOWN
] ? UP
: DOWN
;
730 Tuplet_bracket::add_column (Grob
*me
, Item
*n
)
732 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("note-columns"), n
);
733 add_bound_item (dynamic_cast<Spanner
*> (me
), n
);
737 Tuplet_bracket::add_tuplet_bracket (Grob
*me
, Grob
*bracket
)
739 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("tuplets"), bracket
);
742 MAKE_SCHEME_CALLBACK (Tuplet_bracket
, calc_cross_staff
, 1);
744 Tuplet_bracket::calc_cross_staff (SCM smob
)
746 Grob
*me
= unsmob_grob (smob
);
747 Grob
*staff_symbol
= 0;
748 extract_grob_set (me
, "note-columns", cols
);
749 bool equally_long
= false;
750 Grob
*par_beam
= parallel_beam (me
, cols
, &equally_long
);
753 return par_beam
->get_property ("cross-staff");
755 for (vsize i
= 0; i
< cols
.size (); i
++)
757 Grob
*stem
= unsmob_grob (cols
[i
]->get_object ("stem"));
761 if (to_boolean (stem
->get_property ("cross-staff")))
764 Grob
*stem_staff
= Staff_symbol_referencer::get_staff_symbol (stem
);
765 if (staff_symbol
&& (stem_staff
!= staff_symbol
))
767 staff_symbol
= stem_staff
;
772 ADD_INTERFACE (Tuplet_bracket
,
773 "A bracket with a number in the middle, used for tuplets."
774 " When the bracket spans a line break, the value of"
775 " @code{break-overshoot} determines how far it extends"
776 " beyond the staff. At a line break, the markups in the"
777 " @code{edge-text} are printed at the edges.",
781 "bracket-visibility "
783 "connect-to-neighbor "
788 "full-length-padding "
789 "full-length-to-extent "