2 tuplet-bracket.cc -- implement Tuplet_bracket
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2005 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.
34 #include "tuplet-bracket.hh"
35 #include "line-interface.hh"
38 #include "font-interface.hh"
39 #include "output-def.hh"
40 #include "text-interface.hh"
42 #include "note-column.hh"
43 #include "pointer-group-interface.hh"
44 #include "directional-element-interface.hh"
46 #include "staff-symbol-referencer.hh"
50 get_x_bound_grob (Grob *g, Direction my_dir)
52 if (Note_column::get_stem (g)
53 && Note_column::dir (g) == my_dir)
55 g = Note_column::get_stem (g);
61 Tuplet_bracket::parallel_beam (Grob *me, Link_array<Grob> const &cols, bool *equally_long)
66 Grob *s1 = Note_column::get_stem (cols[0]);
67 Grob *s2 = Note_column::get_stem (cols.top ());
69 Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
70 Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
72 Spanner *sp = dynamic_cast<Spanner *> (me);
74 *equally_long = false;
75 if (! (b1 && (b1 == b2) && !sp->is_broken ()))
78 extract_grob_set (b1, "stems", beam_stems);
79 if (beam_stems.size () == 0)
81 programming_error ("beam under tuplet bracket has no stems");
86 *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
93 in the case that there is no bracket, but there is a (single) beam,
94 follow beam precisely for determining tuplet number location.
96 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
98 Tuplet_bracket::print (SCM smob)
100 Grob *me = unsmob_grob (smob);
102 extract_grob_set (me, "note-columns", columns);
104 if (!columns.size ())
105 return mol.smobbed_copy ();
108 SCM lp = me->get_property ("left-position");
109 SCM rp = me->get_property ("right-position");
111 if (!scm_is_number (rp) || !scm_is_number (lp))
114 UGH. dependency tracking!
116 extract_grob_set (me, "tuplets", tuplets);
117 for (int i = 0; i < tuplets.size (); i++)
118 Tuplet_bracket::print (tuplets[i]->self_scm());
120 after_line_breaking (smob);
124 Real ly = robust_scm2double (me->get_property ("left-position"), 0);
125 Real ry = robust_scm2double (me->get_property ("right-position"), 0);
127 bool equally_long = false;
128 Grob *par_beam = parallel_beam (me, columns, &equally_long);
130 Spanner *sp = dynamic_cast<Spanner *> (me);
132 bool bracket_visibility = !(par_beam && equally_long);
133 bool number_visibility = true;
136 Fixme: the type of this prop is sucky.
138 SCM bracket = me->get_property ("bracket-visibility");
139 if (scm_is_bool (bracket))
141 bracket_visibility = ly_scm2bool (bracket);
143 else if (bracket == ly_symbol2scm ("if-no-beam"))
144 bracket_visibility = !par_beam;
146 SCM numb = me->get_property ("number-visibility");
147 if (scm_is_bool (numb))
149 number_visibility = ly_scm2bool (numb);
151 else if (numb == ly_symbol2scm ("if-no-beam"))
152 number_visibility = !par_beam;
154 Grob *commonx = columns[0]->common_refpoint (columns.top (), X_AXIS);
157 Tuplet brackets are normally not broken, but we shouldn't crash if
160 commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
161 commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
163 Direction dir = get_grob_direction (me);
165 Grob *lgr = get_x_bound_grob (columns[0], dir);
166 Grob *rgr = get_x_bound_grob (columns.top (), dir);
168 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
169 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
172 SCM number = me->get_property ("text");
174 Output_def *pap = me->get_layout ();
176 if (scm_is_string (number) && number_visibility)
178 SCM properties = Font_interface::text_font_alist_chain (me);
179 SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
180 num = *unsmob_stencil (snum);
181 num.align_to (X_AXIS, CENTER);
182 num.translate_axis (w / 2, X_AXIS);
183 num.align_to (Y_AXIS, CENTER);
185 num.translate_axis ((ry - ly) / 2, Y_AXIS);
187 mol.add_stencil (num);
191 No bracket when it would be smaller than the number.
193 TODO: should use GAP in calculation too.
195 if (bracket_visibility && number_visibility
196 && mol.extent (X_AXIS).length () > w)
198 bracket_visibility = false;
201 if (bracket_visibility)
205 if (!num.extent (X_AXIS).is_empty ())
206 gap = num.extent (X_AXIS).length () + 1.0;
208 Drul_array<Real> zero (0,0);
209 Real ss = Staff_symbol_referencer::staff_space (me);
210 Drul_array<Real> height
211 = robust_scm2drul (me->get_property ("edge-height"), zero);
212 Drul_array<Real> flare
213 = robust_scm2drul (me->get_property ("bracket-flare"), zero);
214 Drul_array<Real> shorten
215 = robust_scm2drul (me->get_property ("shorten-pair"), zero);
217 scale_drul (&height, -ss * dir);
218 scale_drul (&flare, ss);
219 scale_drul (&shorten, ss);
221 Stencil brack = make_bracket (me, Y_AXIS,
225 0.1 = more space at right due to italics
226 TODO: use italic correction of font.
228 Interval (-0.5, 0.5) * gap + 0.1,
230 mol.add_stencil (brack);
233 mol.translate_axis (ly, Y_AXIS);
234 mol.translate_axis (x0 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
235 return mol.smobbed_copy ();
239 should move to lookup?
241 TODO: this will fail for very short (shorter than the flare)
245 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
248 Drul_array<Real> height,
250 Drul_array<Real> flare,
251 Drul_array<Real> shorten)
253 Drul_array<Offset> corners (Offset (0, 0), dz);
255 Real length = dz.length ();
256 Drul_array<Offset> gap_corners;
258 Axis bracket_axis = other_axis (protusion_axis);
260 Drul_array<Offset> straight_corners = corners;
265 straight_corners[d] += -d * shorten[d] / length * dz;
267 while (flip (&d) != LEFT);
270 gap = Interval (0, 0);
273 gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
275 while (flip (&d) != LEFT);
277 Drul_array<Offset> flare_corners = straight_corners;
280 flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
281 flare_corners[d][protusion_axis] += height[d];
282 straight_corners[d][bracket_axis] += -d * flare[d];
284 while (flip (&d) != LEFT);
289 m.add_stencil (Line_interface::line (me, straight_corners[d],
292 m.add_stencil (Line_interface::line (me, straight_corners[d],
295 while (flip (&d) != LEFT);
301 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
303 extract_grob_set (me, "note-columns", columns);
305 while (l < columns.size () && Note_column::has_rests (columns[l]))
308 int r = columns.size ()- 1;
309 while (r >= l && Note_column::has_rests (columns[r]))
323 use first -> last note for slope, and then correct for disturbing
326 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
328 Spanner *me = dynamic_cast<Spanner*> (me_grob);
330 extract_grob_set (me, "note-columns", columns);
331 extract_grob_set (me, "tuplets", tuplets);
333 Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
334 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
335 Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
336 commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
339 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
340 staff = st->extent (commony, Y_AXIS);
342 Direction dir = get_grob_direction (me);
345 Use outer non-rest columns to determine slope
349 get_bounds (me, &left_col, &right_col);
350 if (left_col && right_col)
352 Interval rv = right_col->extent (commony, Y_AXIS);
353 Interval lv = left_col->extent (commony, Y_AXIS);
356 Real graphical_dy = rv[dir] - lv[dir];
358 Slice ls = Note_column::head_positions_interval (left_col);
359 Slice rs = Note_column::head_positions_interval (right_col);
362 musical_dy[UP] = rs[UP] - ls[UP];
363 musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
364 if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
366 else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
374 *offset = -dir * infinity_f;
376 if (!columns.size ())
379 Grob *lgr = get_x_bound_grob (columns[0], dir);
380 Grob *rgr = get_x_bound_grob (columns.top (), dir);
381 Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
382 Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
387 Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
389 Array<Offset> points;
390 for (int i = 0; i < columns.size (); i++)
392 Interval note_ext = columns[i]->extent (commony, Y_AXIS);
393 note_ext.unite (staff);
394 Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
396 Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
397 points.push (Offset (x, notey));
401 This is a slight hack. We compute two encompass points from the
402 bbox of the smaller tuplets.
404 We assume that the smaller bracket is 1.0 space high.
407 Real ss = Staff_symbol_referencer::staff_space (me);
408 for (int i = 0; i < tuplets.size (); i++)
410 Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
411 Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
414 Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
415 Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
416 Real other_dy = rp - lp;
421 tuplet_y.linear_combination (d * sign (other_dy));
425 Let's not take padding into account for nested tuplets.
426 the edges can come very close to the stems, likewise for
429 Drul_array<Real> my_height
430 = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
431 if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
432 == me->get_bound (d))
434 y += dir * my_height[d];
438 points.push (Offset (tuplet_x[d] - x0, y));
440 while (flip (&d) != LEFT);
443 for (int i = 0; i < points.size (); i++)
445 Real x = points[i][X_AXIS];
446 Real tuplety = *dy * x * factor;
448 if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
449 *offset = points[i][Y_AXIS] - tuplety;
452 *offset += scm_to_double (me->get_property ("padding")) * dir;
455 horizontal brackets should not collide with staff lines.
457 Kind of pointless since we put them outside the staff anyway, but
458 let's leave code for the future when possibly allow them to move
459 into the staff once again.
462 fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
464 // quantize, then do collision check.
467 *offset = rint (*offset);
468 if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
476 We depend on the beams if there are any.
478 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
480 Tuplet_bracket::before_line_breaking (SCM smob)
482 Grob *me = unsmob_grob (smob);
483 extract_grob_set (me, "note-columns", columns);
485 for (int i = columns.size (); i--;)
487 Grob *s = Note_column::get_stem (columns[i]);
488 Grob *b = s ? Stem::get_beam (s) : 0;
490 me->add_dependency (b);
492 return SCM_UNSPECIFIED;
495 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
498 Tuplet_bracket::after_line_breaking (SCM smob)
500 Grob *me = unsmob_grob (smob);
501 extract_grob_set (me, "note-columns", columns);
503 if (!columns.size ())
506 return SCM_UNSPECIFIED;
508 if (dynamic_cast<Spanner *> (me)->is_broken ())
510 me->warning (_ ("removing tuplet bracket across linebreak"));
512 return SCM_UNSPECIFIED;
515 Direction dir = get_grob_direction (me);
518 dir = Tuplet_bracket::get_default_dir (me);
519 set_grob_direction (me, dir);
522 bool equally_long = false;
523 Grob *par_beam = parallel_beam (me, columns, &equally_long);
526 We follow the beam only if there is one, and we are next to it.
531 || get_grob_direction (par_beam) != dir)
533 calc_position_and_height (me, &offset, &dy);
537 SCM ps = par_beam->get_property ("positions");
539 Real lp = scm_to_double (scm_car (ps));
540 Real rp = scm_to_double (scm_cdr (ps));
545 offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
549 SCM lp = me->get_property ("left-position");
550 SCM rp = me->get_property ("right-position");
552 if (scm_is_number (lp) && !scm_is_number (rp))
554 rp = scm_from_double (scm_to_double (lp) + dy);
556 else if (scm_is_number (rp) && !scm_is_number (lp))
558 lp = scm_from_double (scm_to_double (rp) - dy);
560 else if (!scm_is_number (rp) && !scm_is_number (lp))
562 lp = scm_from_double (offset);
563 rp = scm_from_double (offset + dy);
566 me->set_property ("left-position", lp);
567 me->set_property ("right-position", rp);
569 return SCM_UNSPECIFIED;
576 Tuplet_bracket::get_default_dir (Grob *me)
578 Drul_array<int> dirs (0, 0);
579 extract_grob_set (me, "note-columns", columns);
580 for (int i = 0 ; i < columns.size (); i++)
582 Grob *nc = columns[i];
583 Direction d = Note_column::dir (nc);
588 return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
592 Tuplet_bracket::add_column (Grob *me, Item *n)
594 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
595 me->add_dependency (n);
597 add_bound_item (dynamic_cast<Spanner *> (me), n);
601 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
603 Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
604 me->add_dependency (bracket);
609 ADD_INTERFACE (Tuplet_bracket,
610 "tuplet-bracket-interface",
611 "A bracket with a number in the middle, used for tuplets.",
613 "note-columns bracket-flare edge-height shorten-pair "
615 "padding left-position right-position bracket-visibility "
616 "number-visibility thickness direction");