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"
46 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
48 Slur::calc_direction (SCM smob)
50 Grob *me = unsmob<Grob> (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 = unsmob<Grob> (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 = unsmob<Grob> (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 = unsmob<Grob> (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 = *unsmob<Stencil> (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 && scm_is_eq (breakables[j]->get_property ("avoid-slur"),
215 ly_symbol2scm ("inside")))
216 new_encompasses.push_back (breakables[j]);
219 new_encompasses.push_back (g);
222 SCM encompass_scm = me->get_object ("encompass-objects");
223 if (unsmob<Grob_array> (encompass_scm))
226 = unsmob<Grob_array> (encompass_scm)->array_reference ();
227 arr = new_encompasses;
232 Slur::get_curve (Grob *me)
236 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
238 b.control_[i++] = ly_scm2offset (scm_car (s));
244 Slur::add_column (Grob *me, Grob *n)
246 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
247 add_bound_item (dynamic_cast<Spanner *> (me), n);
251 Slur::add_extra_encompass (Grob *me, Grob *n)
253 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
256 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
258 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
260 int start = robust_scm2int (start_scm, 0);
261 int end = robust_scm2int (end_scm, 0);
262 Grob *script = unsmob<Grob> (grob);
263 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
267 SCM avoid = script->get_property ("avoid-slur");
268 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
269 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
272 Real offset = robust_scm2double (offset_scm, 0.0);
273 Direction dir = get_grob_direction (script);
274 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
277 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
279 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
281 Grob *script = unsmob<Grob> (grob);
282 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
287 SCM avoid = script->get_property ("avoid-slur");
288 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
289 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
292 Direction dir = get_grob_direction (script);
296 Grob *cx = script->common_refpoint (slur, X_AXIS);
297 Grob *cy = script->common_refpoint (slur, Y_AXIS);
299 Bezier curve = Slur::get_curve (slur);
301 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
302 slur->relative_coordinate (cy, Y_AXIS)));
304 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
305 Interval xext = robust_relative_extent (script, cx, X_AXIS);
306 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
309 cannot use is_empty because some 0-extent scripts
310 come up with TabStaffs.
312 if (xext.length () <= 0 || yext.length () <= 0)
315 bool contains = false;
316 for (LEFT_and_RIGHT (d))
317 contains |= slur_wid.contains (xext[d]);
322 Real offset = robust_scm2double (offset_scm, 0);
323 yext.translate (offset);
325 /* FIXME: slur property, script property? */
326 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
328 yext.widen (slur_padding);
330 Interval exts[] = {xext, yext};
331 bool do_shift = false;
333 if (scm_is_eq (avoid, ly_symbol2scm ("outside")))
335 for (LEFT_and_RIGHT (d))
337 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
338 Real y = curve.get_other_coordinate (X_AXIS, x);
339 do_shift = y == minmax (dir, yext[-dir], y);
346 for (int a = X_AXIS; a < NO_AXES; a++)
348 for (LEFT_and_RIGHT (d))
350 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
351 for (vsize i = 0; i < coords.size (); i++)
353 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
365 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;
367 return scm_from_double (offset + avoidance_offset);
370 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
372 Slur::vertical_skylines (SCM smob)
374 Grob *me = unsmob<Grob> (smob);
378 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
380 Bezier curve = Slur::get_curve (me);
381 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
382 for (vsize i = 0; i < box_count; i++)
385 b.add_point (curve.curve_point (i * 1.0 / box_count));
386 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
390 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
394 * Used by Slur_engraver:: and Phrasing_slur_engraver::
397 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
398 vector<Grob *> &slurs,
399 vector<Grob *> &end_slurs)
401 if (slurs.empty () && end_slurs.empty ())
404 Grob *e = info.grob ();
405 SCM avoid = e->get_property ("avoid-slur");
407 if (end_slurs.size () && !slurs.size ())
412 if (Tie::has_interface (e)
413 || scm_is_eq (avoid, ly_symbol2scm ("inside")))
415 for (vsize i = slurs.size (); i--;)
416 add_extra_encompass (slurs[i], e);
417 for (vsize i = end_slurs.size (); i--;)
418 add_extra_encompass (end_slurs[i], e);
420 e->set_object ("slur", slur->self_scm ());
422 else if (scm_is_eq (avoid, ly_symbol2scm ("outside"))
423 || scm_is_eq (avoid, ly_symbol2scm ("around")))
427 chain_offset_callback (e,
428 Unpure_pure_container::make_smob (outside_slur_callback_proc,
429 pure_outside_slur_callback_proc),
431 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
432 e->set_object ("slur", slur->self_scm ());
435 else if (!scm_is_eq (avoid, ly_symbol2scm ("ignore")))
436 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
437 e->name ().c_str ()));
441 A callback that will be chained together with the original cross-staff
442 value of a grob that is placed 'outside or 'around a slur. This just says
443 that any grob becomes cross-staff if it is placed 'outside or 'around a
446 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
448 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
450 if (to_boolean (previous))
453 Grob *me = unsmob<Grob> (smob);
454 Grob *slur = unsmob<Grob> (me->get_object ("slur"));
458 return slur->get_property ("cross-staff");
461 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
463 Slur::calc_cross_staff (SCM smob)
465 Grob *me = unsmob<Grob> (smob);
467 extract_grob_set (me, "note-columns", cols);
468 extract_grob_set (me, "encompass-objects", extras);
470 for (vsize i = 0; i < cols.size (); i++)
472 if (Grob *s = Note_column::get_stem (cols[i]))
473 if (to_boolean (s->get_property ("cross-staff")))
477 /* the separation items are dealt with in replace_breakable_encompass_objects
478 so we can ignore them here */
479 vector<Grob *> non_sep_extras;
480 for (vsize i = 0; i < extras.size (); i++)
481 if (!Separation_item::has_interface (extras[i]))
482 non_sep_extras.push_back (extras[i]);
484 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
485 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
487 return scm_from_bool (common != me->get_parent (Y_AXIS));
493 "The following properties may be set in the @code{details}"
497 "@item region-size\n"
498 "Size of region (in staff spaces) for determining"
499 " potential endpoints in the Y direction.\n"
500 "@item head-encompass-penalty\n"
501 "Demerit to apply when note heads collide with a slur.\n"
502 "@item stem-encompass-penalty\n"
503 "Demerit to apply when stems collide with a slur.\n"
504 "@item edge-attraction-factor\n"
505 "Factor used to calculate the demerit for distances"
506 " between slur endpoints and their corresponding base"
508 "@item same-slope-penalty\n"
509 "Demerit for slurs with attachment points that are"
510 " horizontally aligned.\n"
511 "@item steeper-slope-factor\n"
512 "Factor used to calculate demerit only if this slur is"
514 "@item non-horizontal-penalty\n"
515 "Demerit for slurs with attachment points that are not"
516 " horizontally aligned.\n"
518 "The maximum slope allowed for this slur.\n"
519 "@item max-slope-factor\n"
520 "Factor that calculates demerit based on the max slope.\n"
521 "@item free-head-distance\n"
522 "The amount of vertical free space that must exist"
523 " between a slur and note heads.\n"
524 "@item absolute-closeness-measure\n"
525 "Factor to calculate demerit for variance between a note"
527 "@item extra-object-collision-penalty\n"
528 "Factor to calculate demerit for extra objects that the"
529 " slur encompasses, including accidentals, fingerings, and"
531 "@item accidental-collision\n"
532 "Factor to calculate demerit for @code{Accidental} objects"
533 " that the slur encompasses. This property value replaces"
534 " the value of @code{extra-object-collision-penalty}.\n"
535 "@item extra-encompass-free-distance\n"
536 "The amount of vertical free space that must exist"
537 " between a slur and various objects it encompasses,"
538 " including accidentals, fingerings, and tuplet numbers.\n"
539 "@item extra-encompass-collision-distance\n"
540 "This detail is currently unused.\n"
541 "@item head-slur-distance-factor\n"
542 "Factor to calculate demerit for variance between a note"
544 "@item head-slur-distance-max-ratio\n"
545 "The maximum value for the ratio of distance between a"
546 " note head and slur.\n"
547 "@item gap-to-staffline-inside\n"
548 "Minimum gap inside the curve of the slur"
549 " where the slur is parallel to a staffline.\n"
550 "@item gap-to-staffline-outside\n"
551 "Minimum gap outside the curve of the slur"
552 " where the slur is parallel to a staffline.\n"
553 "@item free-slur-distance\n"
554 "The amount of vertical free space that must exist"
555 " between adjacent slurs. This subproperty only works"
556 " for @code{PhrasingSlur}.\n"
557 "@item edge-slope-exponent\n"
558 "Factor used to calculate the demerit for the slope of"
559 " a slur near its endpoints; a larger value yields a"
565 "avoid-slur " /* UGH. */