2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2014 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"
46 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
48 Slur::calc_direction (SCM smob)
50 Grob *me = Grob::unsmob (smob);
51 extract_grob_set (me, "note-columns", encompasses);
53 if (encompasses.empty ())
60 for (vsize i = 0; i < encompasses.size (); i++)
62 if (Note_column::dir (encompasses[i]) < 0)
68 return scm_from_int (d);
71 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
73 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
76 Note that this estimation uses a rote add-on of 0.5 to the
77 highest encompassed note-head for a slur estimate. This is,
78 in most cases, shorter than the actual slur.
80 Ways to improve this could include:
81 -- adding extra height for scripts that avoid slurs on the inside
82 -- adding extra height for the "bulge" in a slur above a note head
84 Grob *me = Grob::unsmob (smob);
85 int start = scm_to_int (start_scm);
86 int end = scm_to_int (end_scm);
87 Direction dir = get_grob_direction (me);
89 extract_grob_set (me, "note-columns", encompasses);
93 Grob *parent = me->get_parent (Y_AXIS);
94 Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
95 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
96 /* this could happen if, for example, we are a cross-staff slur.
97 in this case, we want to be ignored */
98 return ly_interval2scm (Interval ());
100 for (vsize i = 0; i < encompasses.size (); i++)
102 Interval d = encompasses[i]->pure_height (parent, start, end);
105 for (DOWN_and_UP (downup))
106 ret.add_point (d[dir]);
108 if (extremal_heights[LEFT] == infinity_f)
109 extremal_heights[LEFT] = d[dir];
110 extremal_heights[RIGHT] = d[dir];
115 return ly_interval2scm (Interval ());
117 Interval extremal_span;
118 extremal_span.set_empty ();
119 for (LEFT_and_RIGHT (d))
120 extremal_span.add_point (extremal_heights[d]);
121 ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
124 The +0.5 comes from the fact that we try to place a slur
125 0.5 staff spaces from the note-head.
126 (see Slur_score_state.get_base_attachments ())
129 return ly_interval2scm (ret);
132 MAKE_SCHEME_CALLBACK (Slur, height, 1);
134 Slur::height (SCM smob)
136 Grob *me = Grob::unsmob (smob);
139 Stencil *m = me->get_stencil ();
140 return m ? ly_interval2scm (m->extent (Y_AXIS))
141 : ly_interval2scm (Interval ());
144 MAKE_SCHEME_CALLBACK (Slur, print, 1);
146 Slur::print (SCM smob)
148 Grob *me = Grob::unsmob (smob);
149 extract_grob_set (me, "note-columns", encompasses);
150 if (encompasses.empty ())
156 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
157 Real base_thick = staff_thick
158 * robust_scm2double (me->get_property ("thickness"), 1);
159 Real line_thick = staff_thick
160 * robust_scm2double (me->get_property ("line-thickness"), 1);
162 Bezier one = get_curve (me);
165 SCM dash_definition = me->get_property ("dash-definition");
166 a = Lookup::slur (one,
167 get_grob_direction (me) * base_thick,
171 #if DEBUG_SLUR_SCORING
172 SCM annotation = me->get_property ("annotation");
173 if (scm_is_string (annotation))
176 SCM properties = Font_interface::text_font_alist_chain (me);
178 if (!scm_is_number (me->get_property ("font-size")))
179 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
182 Stencil tm = *Stencil::unsmob (Text_interface::interpret_markup
183 (me->layout ()->self_scm (), properties,
185 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
189 return a.smobbed_copy ();
193 it would be better to do this at engraver level, but that is
194 fragile, as the breakable items are generated on staff level, at
195 which point slur starts and ends have to be tracked
198 Slur::replace_breakable_encompass_objects (Grob *me)
200 extract_grob_set (me, "encompass-objects", extra_objects);
201 vector<Grob *> new_encompasses;
203 for (vsize i = 0; i < extra_objects.size (); i++)
205 Grob *g = extra_objects[i];
207 if (Separation_item::has_interface (g))
209 extract_grob_set (g, "elements", breakables);
210 for (vsize j = 0; j < breakables.size (); j++)
211 /* if we encompass a separation-item that spans multiple staves,
212 we filter out the grobs that don't belong to our staff */
213 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
214 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
215 new_encompasses.push_back (breakables[j]);
218 new_encompasses.push_back (g);
221 SCM encompass_scm = me->get_object ("encompass-objects");
222 if (Grob_array::unsmob (encompass_scm))
225 = Grob_array::unsmob (encompass_scm)->array_reference ();
226 arr = new_encompasses;
231 Slur::get_curve (Grob *me)
235 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
237 b.control_[i++] = ly_scm2offset (scm_car (s));
243 Slur::add_column (Grob *me, Grob *n)
245 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
246 add_bound_item (dynamic_cast<Spanner *> (me), n);
250 Slur::add_extra_encompass (Grob *me, Grob *n)
252 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
255 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
257 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
259 int start = robust_scm2int (start_scm, 0);
260 int end = robust_scm2int (end_scm, 0);
261 Grob *script = Grob::unsmob (grob);
262 Grob *slur = Grob::unsmob (script->get_object ("slur"));
266 SCM avoid = script->get_property ("avoid-slur");
267 if (avoid != ly_symbol2scm ("outside") && 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_height (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 = Grob::unsmob (grob);
280 Grob *slur = Grob::unsmob (script->get_object ("slur"));
285 SCM avoid = script->get_property ("avoid-slur");
286 if (avoid != ly_symbol2scm ("outside")
287 && 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 (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 = Grob::unsmob (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 (Tie::has_interface (e)
411 || 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 (avoid == ly_symbol2scm ("outside")
421 || avoid == ly_symbol2scm ("around"))
425 chain_offset_callback (e,
426 ly_make_unpure_pure_container (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 (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 (previous == SCM_BOOL_T)
451 Grob *me = Grob::unsmob (smob);
452 Grob *slur = Grob::unsmob (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 = Grob::unsmob (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 (!Separation_item::has_interface (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. */