2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
7 LilyPond is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 LilyPond is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
22 #include "grob-info.hh"
23 #include "grob-array.hh"
26 #include "directional-element-interface.hh"
27 #include "font-interface.hh"
29 #include "pointer-group-interface.hh"
31 #include "main.hh" // DEBUG_SLUR_SCORING
32 #include "note-column.hh"
33 #include "output-def.hh"
35 #include "skyline-pair.hh"
36 #include "staff-symbol-referencer.hh"
38 #include "text-interface.hh"
41 #include "slur-scoring.hh"
42 #include "separation-item.hh"
43 #include "international.hh"
45 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
47 Slur::calc_direction (SCM smob)
49 Grob *me = unsmob_grob (smob);
50 extract_grob_set (me, "note-columns", encompasses);
52 if (encompasses.empty ())
59 for (vsize i = 0; i < encompasses.size (); i++)
61 if (Note_column::dir (encompasses[i]) < 0)
67 return scm_from_int (d);
70 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
72 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
75 Note that this estimation uses a rote add-on of 0.5 to the
76 highest encompassed note-head for a slur estimate. This is,
77 in most cases, shorter than the actual slur.
79 Ways to improve this could include:
80 -- adding extra height for scripts that avoid slurs on the inside
81 -- adding extra height for the "bulge" in a slur above a note head
83 Grob *me = unsmob_grob (smob);
84 int start = scm_to_int (start_scm);
85 int end = scm_to_int (end_scm);
86 Direction dir = get_grob_direction (me);
88 extract_grob_set (me, "note-columns", encompasses);
92 Grob *parent = me->get_parent (Y_AXIS);
93 Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
94 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
95 /* this could happen if, for example, we are a cross-staff slur.
96 in this case, we want to be ignored */
97 return ly_interval2scm (Interval ());
99 for (vsize i = 0; i < encompasses.size (); i++)
101 Interval d = encompasses[i]->pure_height (parent, start, end);
104 for (DOWN_and_UP (downup))
105 ret.add_point (d[dir]);
107 if (extremal_heights[LEFT] == infinity_f)
108 extremal_heights[LEFT] = d[dir];
109 extremal_heights[RIGHT] = d[dir];
114 return ly_interval2scm (Interval ());
116 Interval extremal_span;
117 extremal_span.set_empty ();
118 for (LEFT_and_RIGHT (d))
119 extremal_span.add_point (extremal_heights[d]);
120 ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
123 The +0.5 comes from the fact that we try to place a slur
124 0.5 staff spaces from the note-head.
125 (see Slur_score_state.get_base_attachments ())
128 return ly_interval2scm (ret);
131 MAKE_SCHEME_CALLBACK (Slur, height, 1);
133 Slur::height (SCM smob)
135 Grob *me = unsmob_grob (smob);
138 Stencil *m = me->get_stencil ();
139 return m ? ly_interval2scm (m->extent (Y_AXIS))
140 : ly_interval2scm (Interval ());
143 MAKE_SCHEME_CALLBACK (Slur, print, 1);
145 Slur::print (SCM smob)
147 Grob *me = unsmob_grob (smob);
148 extract_grob_set (me, "note-columns", encompasses);
149 if (encompasses.empty ())
155 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
156 Real base_thick = staff_thick
157 * robust_scm2double (me->get_property ("thickness"), 1);
158 Real line_thick = staff_thick
159 * robust_scm2double (me->get_property ("line-thickness"), 1);
161 Bezier one = get_curve (me);
164 SCM dash_definition = me->get_property ("dash-definition");
165 a = Lookup::slur (one,
166 get_grob_direction (me) * base_thick,
170 #if DEBUG_SLUR_SCORING
171 SCM annotation = me->get_property ("annotation");
172 if (scm_is_string (annotation))
175 SCM properties = Font_interface::text_font_alist_chain (me);
177 if (!scm_is_number (me->get_property ("font-size")))
178 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
181 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
182 (me->layout ()->self_scm (), properties,
184 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
188 return a.smobbed_copy ();
192 it would be better to do this at engraver level, but that is
193 fragile, as the breakable items are generated on staff level, at
194 which point slur starts and ends have to be tracked
197 Slur::replace_breakable_encompass_objects (Grob *me)
199 extract_grob_set (me, "encompass-objects", extra_objects);
200 vector<Grob *> new_encompasses;
202 for (vsize i = 0; i < extra_objects.size (); i++)
204 Grob *g = extra_objects[i];
206 if (Separation_item::has_interface (g))
208 extract_grob_set (g, "elements", breakables);
209 for (vsize j = 0; j < breakables.size (); j++)
210 /* if we encompass a separation-item that spans multiple staves,
211 we filter out the grobs that don't belong to our staff */
212 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
213 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
214 new_encompasses.push_back (breakables[j]);
217 new_encompasses.push_back (g);
220 SCM encompass_scm = me->get_object ("encompass-objects");
221 if (Grob_array::unsmob (encompass_scm))
224 = unsmob_grob_array (encompass_scm)->array_reference ();
225 arr = new_encompasses;
230 Slur::get_curve (Grob *me)
234 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
236 b.control_[i++] = ly_scm2offset (scm_car (s));
242 Slur::add_column (Grob *me, Grob *n)
244 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
245 add_bound_item (dynamic_cast<Spanner *> (me), n);
249 Slur::add_extra_encompass (Grob *me, Grob *n)
251 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
254 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
256 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
258 int start = robust_scm2int (start_scm, 0);
259 int end = robust_scm2int (end_scm, 0);
260 Grob *script = unsmob_grob (grob);
261 Grob *slur = unsmob_grob (script->get_object ("slur"));
265 SCM avoid = script->get_property ("avoid-slur");
266 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
269 Real offset = robust_scm2double (offset_scm, 0.0);
270 Direction dir = get_grob_direction (script);
271 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
274 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
276 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
278 Grob *script = unsmob_grob (grob);
279 Grob *slur = unsmob_grob (script->get_object ("slur"));
284 SCM avoid = script->get_property ("avoid-slur");
285 if (avoid != ly_symbol2scm ("outside")
286 && avoid != ly_symbol2scm ("around"))
289 Direction dir = get_grob_direction (script);
293 Grob *cx = script->common_refpoint (slur, X_AXIS);
294 Grob *cy = script->common_refpoint (slur, Y_AXIS);
296 Bezier curve = Slur::get_curve (slur);
298 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
299 slur->relative_coordinate (cy, Y_AXIS)));
301 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
302 Interval xext = robust_relative_extent (script, cx, X_AXIS);
303 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
306 cannot use is_empty because some 0-extent scripts
307 come up with TabStaffs.
309 if (xext.length () <= 0 || yext.length () <= 0)
312 bool contains = false;
313 for (LEFT_and_RIGHT (d))
314 contains |= slur_wid.contains (xext[d]);
319 Real offset = robust_scm2double (offset_scm, 0);
320 yext.translate (offset);
322 /* FIXME: slur property, script property? */
323 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
325 yext.widen (slur_padding);
327 Interval exts[] = {xext, yext};
328 bool do_shift = false;
330 if (avoid == ly_symbol2scm ("outside"))
332 for (LEFT_and_RIGHT (d))
334 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
335 Real y = curve.get_other_coordinate (X_AXIS, x);
336 do_shift = y == minmax (dir, yext[-dir], y);
343 for (int a = X_AXIS; a < NO_AXES; a++)
345 for (LEFT_and_RIGHT (d))
347 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
348 for (vsize i = 0; i < coords.size (); i++)
350 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
362 Real avoidance_offset = do_shift ? curve.minmax (X_AXIS, max (xext[LEFT], curve.control_[0][X_AXIS] + EPS), min (xext[RIGHT], curve.control_[3][X_AXIS] - EPS), dir) - yext[-dir] : 0.0;
364 return scm_from_double (offset + avoidance_offset);
367 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, vertical_skylines, 1, 0, "");
369 Slur::vertical_skylines (SCM smob)
371 Grob *me = unsmob_grob (smob);
375 return Skyline_pair (boxes, 0.0, X_AXIS).smobbed_copy ();
377 Bezier curve = Slur::get_curve (me);
378 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
379 for (vsize i = 0; i < box_count; i++)
382 b.add_point (curve.curve_point (i * 1.0 / box_count));
383 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
387 return Skyline_pair (boxes, 0.0, X_AXIS).smobbed_copy ();
391 * Used by Slur_engraver:: and Phrasing_slur_engraver::
394 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
395 vector<Grob *> &slurs,
396 vector<Grob *> &end_slurs)
398 if (slurs.empty () && end_slurs.empty ())
401 Grob *e = info.grob ();
402 SCM avoid = e->get_property ("avoid-slur");
403 if (Tie::has_interface (e)
404 || avoid == ly_symbol2scm ("inside"))
406 for (vsize i = slurs.size (); i--;)
407 add_extra_encompass (slurs[i], e);
408 for (vsize i = end_slurs.size (); i--;)
409 add_extra_encompass (end_slurs[i], e);
411 else if (avoid == ly_symbol2scm ("outside")
412 || avoid == ly_symbol2scm ("around"))
415 if (end_slurs.size () && !slurs.size ())
422 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
423 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
424 e->set_object ("slur", slur->self_scm ());
427 else if (avoid != ly_symbol2scm ("ignore"))
428 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
429 e->name ().c_str ()));
433 A callback that will be chained together with the original cross-staff
434 value of a grob that is placed 'outside or 'around a slur. This just says
435 that any grob becomes cross-staff if it is placed 'outside or 'around a
438 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
440 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
442 if (previous == SCM_BOOL_T)
445 Grob *me = unsmob_grob (smob);
446 Grob *slur = unsmob_grob (me->get_object ("slur"));
450 return slur->get_property ("cross-staff");
453 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
455 Slur::calc_cross_staff (SCM smob)
457 Grob *me = unsmob_grob (smob);
459 extract_grob_set (me, "note-columns", cols);
460 extract_grob_set (me, "encompass-objects", extras);
462 for (vsize i = 0; i < cols.size (); i++)
464 if (Grob *s = Note_column::get_stem (cols[i]))
465 if (to_boolean (s->get_property ("cross-staff")))
469 /* the separation items are dealt with in replace_breakable_encompass_objects
470 so we can ignore them here */
471 vector<Grob *> non_sep_extras;
472 for (vsize i = 0; i < extras.size (); i++)
473 if (!Separation_item::has_interface (extras[i]))
474 non_sep_extras.push_back (extras[i]);
476 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
477 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
479 return scm_from_bool (common != me->get_parent (Y_AXIS));
485 "The following properties may be set in the @code{details}"
489 "@item region-size\n"
490 "Size of region (in staff spaces) for determining"
491 " potential endpoints in the Y direction.\n"
492 "@item head-encompass-penalty\n"
493 "Demerit to apply when note heads collide with a slur.\n"
494 "@item stem-encompass-penalty\n"
495 "Demerit to apply when stems collide with a slur.\n"
496 "@item edge-attraction-factor\n"
497 "Factor used to calculate the demerit for distances"
498 " between slur endpoints and their corresponding base"
500 "@item same-slope-penalty\n"
501 "Demerit for slurs with attachment points that are"
502 " horizontally aligned.\n"
503 "@item steeper-slope-factor\n"
504 "Factor used to calculate demerit only if this slur is"
506 "@item non-horizontal-penalty\n"
507 "Demerit for slurs with attachment points that are not"
508 " horizontally aligned.\n"
510 "The maximum slope allowed for this slur.\n"
511 "@item max-slope-factor\n"
512 "Factor that calculates demerit based on the max slope.\n"
513 "@item free-head-distance\n"
514 "The amount of vertical free space that must exist"
515 " between a slur and note heads.\n"
516 "@item absolute-closeness-measure\n"
517 "Factor to calculate demerit for variance between a note"
519 "@item extra-object-collision-penalty\n"
520 "Factor to calculate demerit for extra objects that the"
521 " slur encompasses, including accidentals, fingerings, and"
523 "@item accidental-collision\n"
524 "Factor to calculate demerit for @code{Accidental} objects"
525 " that the slur encompasses. This property value replaces"
526 " the value of @code{extra-object-collision-penalty}.\n"
527 "@item extra-encompass-free-distance\n"
528 "The amount of vertical free space that must exist"
529 " between a slur and various objects it encompasses,"
530 " including accidentals, fingerings, and tuplet numbers.\n"
531 "@item extra-encompass-collision-distance\n"
532 "This detail is currently unused.\n"
533 "@item head-slur-distance-factor\n"
534 "Factor to calculate demerit for variance between a note"
536 "@item head-slur-distance-max-ratio\n"
537 "The maximum value for the ratio of distance between a"
538 " note head and slur.\n"
539 "@item free-slur-distance\n"
540 "The amount of vertical free space that must exist"
541 " between adjacent slurs. This subproperty only works"
542 " for @code{PhrasingSlur}.\n"
543 "@item edge-slope-exponent\n"
544 "Factor used to calculate the demerit for the slope of"
545 " a slur near its endpoints; a larger value yields a"
551 "avoid-slur " /* UGH. */
563 "skyline-quantizing "