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_y_extent (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 (has_interface<Separation_item> (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 if (Grob_array *a = unsmob<Grob_array> (me->get_object ("encompass-objects")))
223 a->set_array (new_encompasses);
227 Slur::get_curve (Grob *me)
231 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
233 b.control_[i++] = ly_scm2offset (scm_car (s));
239 Slur::add_column (Grob *me, Grob *n)
241 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
242 add_bound_item (dynamic_cast<Spanner *> (me), n);
246 Slur::add_extra_encompass (Grob *me, Grob *n)
248 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
251 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
253 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
255 int start = robust_scm2int (start_scm, 0);
256 int end = robust_scm2int (end_scm, 0);
257 Grob *script = unsmob<Grob> (grob);
258 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
262 SCM avoid = script->get_property ("avoid-slur");
263 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
264 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
267 Real offset = robust_scm2double (offset_scm, 0.0);
268 Direction dir = get_grob_direction (script);
269 return scm_from_double (offset + dir * slur->pure_y_extent (slur, start, end).length () / 4);
272 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
274 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
276 Grob *script = unsmob<Grob> (grob);
277 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
282 SCM avoid = script->get_property ("avoid-slur");
283 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
284 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
287 Direction dir = get_grob_direction (script);
291 Grob *cx = script->common_refpoint (slur, X_AXIS);
292 Grob *cy = script->common_refpoint (slur, Y_AXIS);
294 Bezier curve = Slur::get_curve (slur);
296 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
297 slur->relative_coordinate (cy, Y_AXIS)));
299 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
300 Interval xext = robust_relative_extent (script, cx, X_AXIS);
301 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
304 cannot use is_empty because some 0-extent scripts
305 come up with TabStaffs.
307 if (xext.length () <= 0 || yext.length () <= 0)
310 bool contains = false;
311 for (LEFT_and_RIGHT (d))
312 contains |= slur_wid.contains (xext[d]);
317 Real offset = robust_scm2double (offset_scm, 0);
318 yext.translate (offset);
320 /* FIXME: slur property, script property? */
321 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
323 yext.widen (slur_padding);
325 Interval exts[] = {xext, yext};
326 bool do_shift = false;
328 if (scm_is_eq (avoid, ly_symbol2scm ("outside")))
330 for (LEFT_and_RIGHT (d))
332 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
333 Real y = curve.get_other_coordinate (X_AXIS, x);
334 do_shift = y == minmax (dir, yext[-dir], y);
341 for (int a = X_AXIS; a < NO_AXES; a++)
343 for (LEFT_and_RIGHT (d))
345 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
346 for (vsize i = 0; i < coords.size (); i++)
348 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
360 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;
362 return scm_from_double (offset + avoidance_offset);
365 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
367 Slur::vertical_skylines (SCM smob)
369 Grob *me = unsmob<Grob> (smob);
373 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
375 Bezier curve = Slur::get_curve (me);
376 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
377 for (vsize i = 0; i < box_count; i++)
380 b.add_point (curve.curve_point (i * 1.0 / box_count));
381 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
385 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
389 * Used by Slur_engraver:: and Phrasing_slur_engraver::
392 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
393 vector<Grob *> &slurs,
394 vector<Grob *> &end_slurs)
396 if (slurs.empty () && end_slurs.empty ())
399 Grob *e = info.grob ();
400 SCM avoid = e->get_property ("avoid-slur");
402 if (end_slurs.size () && !slurs.size ())
407 if (has_interface<Tie> (e)
408 || scm_is_eq (avoid, ly_symbol2scm ("inside")))
410 for (vsize i = slurs.size (); i--;)
411 add_extra_encompass (slurs[i], e);
412 for (vsize i = end_slurs.size (); i--;)
413 add_extra_encompass (end_slurs[i], e);
415 e->set_object ("slur", slur->self_scm ());
417 else if (scm_is_eq (avoid, ly_symbol2scm ("outside"))
418 || scm_is_eq (avoid, ly_symbol2scm ("around")))
422 chain_offset_callback (e,
423 Unpure_pure_container::make_smob (outside_slur_callback_proc,
424 pure_outside_slur_callback_proc),
426 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
427 e->set_object ("slur", slur->self_scm ());
430 else if (!scm_is_eq (avoid, ly_symbol2scm ("ignore")))
431 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
432 e->name ().c_str ()));
436 A callback that will be chained together with the original cross-staff
437 value of a grob that is placed 'outside or 'around a slur. This just says
438 that any grob becomes cross-staff if it is placed 'outside or 'around a
441 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
443 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
445 if (to_boolean (previous))
448 Grob *me = unsmob<Grob> (smob);
449 Grob *slur = unsmob<Grob> (me->get_object ("slur"));
453 return slur->get_property ("cross-staff");
456 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
458 Slur::calc_cross_staff (SCM smob)
460 Grob *me = unsmob<Grob> (smob);
462 extract_grob_set (me, "note-columns", cols);
463 extract_grob_set (me, "encompass-objects", extras);
465 for (vsize i = 0; i < cols.size (); i++)
467 if (Grob *s = Note_column::get_stem (cols[i]))
468 if (to_boolean (s->get_property ("cross-staff")))
472 /* the separation items are dealt with in replace_breakable_encompass_objects
473 so we can ignore them here */
474 vector<Grob *> non_sep_extras;
475 for (vsize i = 0; i < extras.size (); i++)
476 if (!has_interface<Separation_item> (extras[i]))
477 non_sep_extras.push_back (extras[i]);
479 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
480 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
482 return scm_from_bool (common != me->get_parent (Y_AXIS));
488 "The following properties may be set in the @code{details}"
492 "@item region-size\n"
493 "Size of region (in staff spaces) for determining"
494 " potential endpoints in the Y direction.\n"
495 "@item head-encompass-penalty\n"
496 "Demerit to apply when note heads collide with a slur.\n"
497 "@item stem-encompass-penalty\n"
498 "Demerit to apply when stems collide with a slur.\n"
499 "@item edge-attraction-factor\n"
500 "Factor used to calculate the demerit for distances"
501 " between slur endpoints and their corresponding base"
503 "@item same-slope-penalty\n"
504 "Demerit for slurs with attachment points that are"
505 " horizontally aligned.\n"
506 "@item steeper-slope-factor\n"
507 "Factor used to calculate demerit only if this slur is"
509 "@item non-horizontal-penalty\n"
510 "Demerit for slurs with attachment points that are not"
511 " horizontally aligned.\n"
513 "The maximum slope allowed for this slur.\n"
514 "@item max-slope-factor\n"
515 "Factor that calculates demerit based on the max slope.\n"
516 "@item free-head-distance\n"
517 "The amount of vertical free space that must exist"
518 " between a slur and note heads.\n"
519 "@item absolute-closeness-measure\n"
520 "Factor to calculate demerit for variance between a note"
522 "@item extra-object-collision-penalty\n"
523 "Factor to calculate demerit for extra objects that the"
524 " slur encompasses, including accidentals, fingerings, and"
526 "@item accidental-collision\n"
527 "Factor to calculate demerit for @code{Accidental} objects"
528 " that the slur encompasses. This property value replaces"
529 " the value of @code{extra-object-collision-penalty}.\n"
530 "@item extra-encompass-free-distance\n"
531 "The amount of vertical free space that must exist"
532 " between a slur and various objects it encompasses,"
533 " including accidentals, fingerings, and tuplet numbers.\n"
534 "@item extra-encompass-collision-distance\n"
535 "This detail is currently unused.\n"
536 "@item head-slur-distance-factor\n"
537 "Factor to calculate demerit for variance between a note"
539 "@item head-slur-distance-max-ratio\n"
540 "The maximum value for the ratio of distance between a"
541 " note head and slur.\n"
542 "@item gap-to-staffline-inside\n"
543 "Minimum gap inside the curve of the slur"
544 " where the slur is parallel to a staffline.\n"
545 "@item gap-to-staffline-outside\n"
546 "Minimum gap outside the curve of the slur"
547 " where the slur is parallel to a staffline.\n"
548 "@item free-slur-distance\n"
549 "The amount of vertical free space that must exist"
550 " between adjacent slurs. This subproperty only works"
551 " for @code{PhrasingSlur}.\n"
552 "@item edge-slope-exponent\n"
553 "Factor used to calculate the demerit for the slope of"
554 " a slur near its endpoints; a larger value yields a"
560 "avoid-slur " /* UGH. */