2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2015 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"
34 #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 "unpure-pure-container.hh"
44 #include "international.hh"
49 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
51 Slur::calc_direction (SCM smob)
53 Grob *me = unsmob<Grob> (smob);
54 extract_grob_set (me, "note-columns", encompasses);
56 if (encompasses.empty ())
63 for (vsize i = 0; i < encompasses.size (); i++)
65 if (Note_column::dir (encompasses[i]) < 0)
71 return scm_from_int (d);
74 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
76 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
79 Note that this estimation uses a rote add-on of 0.5 to the
80 highest encompassed note-head for a slur estimate. This is,
81 in most cases, shorter than the actual slur.
83 Ways to improve this could include:
84 -- adding extra height for scripts that avoid slurs on the inside
85 -- adding extra height for the "bulge" in a slur above a note head
87 Grob *me = unsmob<Grob> (smob);
88 int start = scm_to_int (start_scm);
89 int end = scm_to_int (end_scm);
90 Direction dir = get_grob_direction (me);
92 extract_grob_set (me, "note-columns", encompasses);
96 Grob *parent = me->get_parent (Y_AXIS);
97 Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
98 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
99 /* this could happen if, for example, we are a cross-staff slur.
100 in this case, we want to be ignored */
101 return ly_interval2scm (Interval ());
103 for (vsize i = 0; i < encompasses.size (); i++)
105 Interval d = encompasses[i]->pure_y_extent (parent, start, end);
108 for (DOWN_and_UP (downup))
109 ret.add_point (d[dir]);
111 if (extremal_heights[LEFT] == infinity_f)
112 extremal_heights[LEFT] = d[dir];
113 extremal_heights[RIGHT] = d[dir];
118 return ly_interval2scm (Interval ());
120 Interval extremal_span;
121 extremal_span.set_empty ();
122 for (LEFT_and_RIGHT (d))
123 extremal_span.add_point (extremal_heights[d]);
124 ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
127 The +0.5 comes from the fact that we try to place a slur
128 0.5 staff spaces from the note-head.
129 (see Slur_score_state.get_base_attachments ())
132 return ly_interval2scm (ret);
135 MAKE_SCHEME_CALLBACK (Slur, height, 1);
137 Slur::height (SCM smob)
139 Grob *me = unsmob<Grob> (smob);
142 Stencil *m = me->get_stencil ();
143 return m ? ly_interval2scm (m->extent (Y_AXIS))
144 : ly_interval2scm (Interval ());
147 MAKE_SCHEME_CALLBACK (Slur, print, 1);
149 Slur::print (SCM smob)
151 Grob *me = unsmob<Grob> (smob);
152 extract_grob_set (me, "note-columns", encompasses);
153 if (encompasses.empty ())
159 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
160 Real base_thick = staff_thick
161 * robust_scm2double (me->get_property ("thickness"), 1);
162 Real line_thick = staff_thick
163 * robust_scm2double (me->get_property ("line-thickness"), 1);
165 Bezier one = get_curve (me);
168 SCM dash_definition = me->get_property ("dash-definition");
169 a = Lookup::slur (one,
170 get_grob_direction (me) * base_thick,
174 #if DEBUG_SLUR_SCORING
175 SCM annotation = me->get_property ("annotation");
176 if (scm_is_string (annotation))
179 SCM properties = Font_interface::text_font_alist_chain (me);
181 if (!scm_is_number (me->get_property ("font-size")))
182 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
185 Stencil tm = *unsmob<Stencil> (Text_interface::interpret_markup
186 (me->layout ()->self_scm (), properties,
188 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
192 return a.smobbed_copy ();
196 it would be better to do this at engraver level, but that is
197 fragile, as the breakable items are generated on staff level, at
198 which point slur starts and ends have to be tracked
201 Slur::replace_breakable_encompass_objects (Grob *me)
203 extract_grob_set (me, "encompass-objects", extra_objects);
204 vector<Grob *> new_encompasses;
206 for (vsize i = 0; i < extra_objects.size (); i++)
208 Grob *g = extra_objects[i];
210 if (has_interface<Separation_item> (g))
212 extract_grob_set (g, "elements", breakables);
213 for (vsize j = 0; j < breakables.size (); j++)
214 /* if we encompass a separation-item that spans multiple staves,
215 we filter out the grobs that don't belong to our staff */
216 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
217 && scm_is_eq (breakables[j]->get_property ("avoid-slur"),
218 ly_symbol2scm ("inside")))
219 new_encompasses.push_back (breakables[j]);
222 new_encompasses.push_back (g);
225 if (Grob_array *a = unsmob<Grob_array> (me->get_object ("encompass-objects")))
226 a->set_array (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 (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
267 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
270 Real offset = robust_scm2double (offset_scm, 0.0);
271 Direction dir = get_grob_direction (script);
272 return scm_from_double (offset + dir * slur->pure_y_extent (slur, start, end).length () / 4);
275 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
277 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
279 Grob *script = unsmob<Grob> (grob);
280 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
285 SCM avoid = script->get_property ("avoid-slur");
286 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
287 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
290 Direction dir = get_grob_direction (script);
294 Grob *cx = script->common_refpoint (slur, X_AXIS);
295 Grob *cy = script->common_refpoint (slur, Y_AXIS);
297 Bezier curve = Slur::get_curve (slur);
299 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
300 slur->relative_coordinate (cy, Y_AXIS)));
302 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
303 Interval xext = robust_relative_extent (script, cx, X_AXIS);
304 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
307 cannot use is_empty because some 0-extent scripts
308 come up with TabStaffs.
310 if (xext.length () <= 0 || yext.length () <= 0)
313 bool contains = false;
314 for (LEFT_and_RIGHT (d))
315 contains |= slur_wid.contains (xext[d]);
320 Real offset = robust_scm2double (offset_scm, 0);
321 yext.translate (offset);
323 /* FIXME: slur property, script property? */
324 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
326 yext.widen (slur_padding);
328 Interval exts[] = {xext, yext};
329 bool do_shift = false;
331 if (scm_is_eq (avoid, ly_symbol2scm ("outside")))
333 for (LEFT_and_RIGHT (d))
335 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
336 Real y = curve.get_other_coordinate (X_AXIS, x);
337 do_shift = y == minmax (dir, yext[-dir], y);
344 for (int a = X_AXIS; a < NO_AXES; a++)
346 for (LEFT_and_RIGHT (d))
348 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
349 for (vsize i = 0; i < coords.size (); i++)
351 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
363 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;
365 return scm_from_double (offset + avoidance_offset);
368 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
370 Slur::vertical_skylines (SCM smob)
372 Grob *me = unsmob<Grob> (smob);
376 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
378 Bezier curve = Slur::get_curve (me);
379 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
380 for (vsize i = 0; i < box_count; i++)
383 b.add_point (curve.curve_point (i * 1.0 / box_count));
384 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
388 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
392 * Used by Slur_engraver:: and Phrasing_slur_engraver::
395 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
396 vector<Grob *> &slurs,
397 vector<Grob *> &end_slurs)
399 if (slurs.empty () && end_slurs.empty ())
402 Grob *e = info.grob ();
403 SCM avoid = e->get_property ("avoid-slur");
405 if (end_slurs.size () && !slurs.size ())
410 if (has_interface<Tie> (e)
411 || scm_is_eq (avoid, ly_symbol2scm ("inside")))
413 for (vsize i = slurs.size (); i--;)
414 add_extra_encompass (slurs[i], e);
415 for (vsize i = end_slurs.size (); i--;)
416 add_extra_encompass (end_slurs[i], e);
418 e->set_object ("slur", slur->self_scm ());
420 else if (scm_is_eq (avoid, ly_symbol2scm ("outside"))
421 || scm_is_eq (avoid, ly_symbol2scm ("around")))
425 chain_offset_callback (e,
426 Unpure_pure_container::make_smob (outside_slur_callback_proc,
427 pure_outside_slur_callback_proc),
429 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
430 e->set_object ("slur", slur->self_scm ());
433 else if (!scm_is_eq (avoid, ly_symbol2scm ("ignore")))
434 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
435 e->name ().c_str ()));
439 A callback that will be chained together with the original cross-staff
440 value of a grob that is placed 'outside or 'around a slur. This just says
441 that any grob becomes cross-staff if it is placed 'outside or 'around a
444 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
446 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
448 if (to_boolean (previous))
451 Grob *me = unsmob<Grob> (smob);
452 Grob *slur = unsmob<Grob> (me->get_object ("slur"));
456 return slur->get_property ("cross-staff");
459 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
461 Slur::calc_cross_staff (SCM smob)
463 Grob *me = unsmob<Grob> (smob);
465 extract_grob_set (me, "note-columns", cols);
466 extract_grob_set (me, "encompass-objects", extras);
468 for (vsize i = 0; i < cols.size (); i++)
470 if (Grob *s = Note_column::get_stem (cols[i]))
471 if (to_boolean (s->get_property ("cross-staff")))
475 /* the separation items are dealt with in replace_breakable_encompass_objects
476 so we can ignore them here */
477 vector<Grob *> non_sep_extras;
478 for (vsize i = 0; i < extras.size (); i++)
479 if (!has_interface<Separation_item> (extras[i]))
480 non_sep_extras.push_back (extras[i]);
482 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
483 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
485 return scm_from_bool (common != me->get_parent (Y_AXIS));
491 "The following properties may be set in the @code{details}"
495 "@item region-size\n"
496 "Size of region (in staff spaces) for determining"
497 " potential endpoints in the Y direction.\n"
498 "@item head-encompass-penalty\n"
499 "Demerit to apply when note heads collide with a slur.\n"
500 "@item stem-encompass-penalty\n"
501 "Demerit to apply when stems collide with a slur.\n"
502 "@item edge-attraction-factor\n"
503 "Factor used to calculate the demerit for distances"
504 " between slur endpoints and their corresponding base"
506 "@item same-slope-penalty\n"
507 "Demerit for slurs with attachment points that are"
508 " horizontally aligned.\n"
509 "@item steeper-slope-factor\n"
510 "Factor used to calculate demerit only if this slur is"
512 "@item non-horizontal-penalty\n"
513 "Demerit for slurs with attachment points that are not"
514 " horizontally aligned.\n"
516 "The maximum slope allowed for this slur.\n"
517 "@item max-slope-factor\n"
518 "Factor that calculates demerit based on the max slope.\n"
519 "@item free-head-distance\n"
520 "The amount of vertical free space that must exist"
521 " between a slur and note heads.\n"
522 "@item absolute-closeness-measure\n"
523 "Factor to calculate demerit for variance between a note"
525 "@item extra-object-collision-penalty\n"
526 "Factor to calculate demerit for extra objects that the"
527 " slur encompasses, including accidentals, fingerings, and"
529 "@item accidental-collision\n"
530 "Factor to calculate demerit for @code{Accidental} objects"
531 " that the slur encompasses. This property value replaces"
532 " the value of @code{extra-object-collision-penalty}.\n"
533 "@item extra-encompass-free-distance\n"
534 "The amount of vertical free space that must exist"
535 " between a slur and various objects it encompasses,"
536 " including accidentals, fingerings, and tuplet numbers.\n"
537 "@item extra-encompass-collision-distance\n"
538 "This detail is currently unused.\n"
539 "@item head-slur-distance-factor\n"
540 "Factor to calculate demerit for variance between a note"
542 "@item head-slur-distance-max-ratio\n"
543 "The maximum value for the ratio of distance between a"
544 " note head and slur.\n"
545 "@item gap-to-staffline-inside\n"
546 "Minimum gap inside the curve of the slur"
547 " where the slur is parallel to a staffline.\n"
548 "@item gap-to-staffline-outside\n"
549 "Minimum gap outside the curve of the slur"
550 " where the slur is parallel to a staffline.\n"
551 "@item free-slur-distance\n"
552 "The amount of vertical free space that must exist"
553 " between adjacent slurs. This subproperty only works"
554 " for @code{PhrasingSlur}.\n"
555 "@item edge-slope-exponent\n"
556 "Factor used to calculate the demerit for the slope of"
557 " a slur near its endpoints; a larger value yields a"
563 "avoid-slur " /* UGH. */