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 SCM encompass_scm = me->get_object ("encompass-objects");
226 if (unsmob<Grob_array> (encompass_scm))
229 = unsmob<Grob_array> (encompass_scm)->array_reference ();
230 arr = new_encompasses;
235 Slur::get_curve (Grob *me)
239 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
241 b.control_[i++] = ly_scm2offset (scm_car (s));
247 Slur::add_column (Grob *me, Grob *n)
249 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
250 add_bound_item (dynamic_cast<Spanner *> (me), n);
254 Slur::add_extra_encompass (Grob *me, Grob *n)
256 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
259 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
261 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
263 int start = robust_scm2int (start_scm, 0);
264 int end = robust_scm2int (end_scm, 0);
265 Grob *script = unsmob<Grob> (grob);
266 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
270 SCM avoid = script->get_property ("avoid-slur");
271 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
272 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
275 Real offset = robust_scm2double (offset_scm, 0.0);
276 Direction dir = get_grob_direction (script);
277 return scm_from_double (offset + dir * slur->pure_y_extent (slur, start, end).length () / 4);
280 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
282 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
284 Grob *script = unsmob<Grob> (grob);
285 Grob *slur = unsmob<Grob> (script->get_object ("slur"));
290 SCM avoid = script->get_property ("avoid-slur");
291 if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
292 && !scm_is_eq (avoid, ly_symbol2scm ("around")))
295 Direction dir = get_grob_direction (script);
299 Grob *cx = script->common_refpoint (slur, X_AXIS);
300 Grob *cy = script->common_refpoint (slur, Y_AXIS);
302 Bezier curve = Slur::get_curve (slur);
304 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
305 slur->relative_coordinate (cy, Y_AXIS)));
307 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
308 Interval xext = robust_relative_extent (script, cx, X_AXIS);
309 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
312 cannot use is_empty because some 0-extent scripts
313 come up with TabStaffs.
315 if (xext.length () <= 0 || yext.length () <= 0)
318 bool contains = false;
319 for (LEFT_and_RIGHT (d))
320 contains |= slur_wid.contains (xext[d]);
325 Real offset = robust_scm2double (offset_scm, 0);
326 yext.translate (offset);
328 /* FIXME: slur property, script property? */
329 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
331 yext.widen (slur_padding);
333 Interval exts[] = {xext, yext};
334 bool do_shift = false;
336 if (scm_is_eq (avoid, ly_symbol2scm ("outside")))
338 for (LEFT_and_RIGHT (d))
340 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
341 Real y = curve.get_other_coordinate (X_AXIS, x);
342 do_shift = y == minmax (dir, yext[-dir], y);
349 for (int a = X_AXIS; a < NO_AXES; a++)
351 for (LEFT_and_RIGHT (d))
353 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
354 for (vsize i = 0; i < coords.size (); i++)
356 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
368 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;
370 return scm_from_double (offset + avoidance_offset);
373 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
375 Slur::vertical_skylines (SCM smob)
377 Grob *me = unsmob<Grob> (smob);
381 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
383 Bezier curve = Slur::get_curve (me);
384 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
385 for (vsize i = 0; i < box_count; i++)
388 b.add_point (curve.curve_point (i * 1.0 / box_count));
389 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
393 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
397 * Used by Slur_engraver:: and Phrasing_slur_engraver::
400 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
401 vector<Grob *> &slurs,
402 vector<Grob *> &end_slurs)
404 if (slurs.empty () && end_slurs.empty ())
407 Grob *e = info.grob ();
408 SCM avoid = e->get_property ("avoid-slur");
410 if (end_slurs.size () && !slurs.size ())
415 if (has_interface<Tie> (e)
416 || scm_is_eq (avoid, ly_symbol2scm ("inside")))
418 for (vsize i = slurs.size (); i--;)
419 add_extra_encompass (slurs[i], e);
420 for (vsize i = end_slurs.size (); i--;)
421 add_extra_encompass (end_slurs[i], e);
423 e->set_object ("slur", slur->self_scm ());
425 else if (scm_is_eq (avoid, ly_symbol2scm ("outside"))
426 || scm_is_eq (avoid, ly_symbol2scm ("around")))
430 chain_offset_callback (e,
431 Unpure_pure_container::make_smob (outside_slur_callback_proc,
432 pure_outside_slur_callback_proc),
434 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
435 e->set_object ("slur", slur->self_scm ());
438 else if (!scm_is_eq (avoid, ly_symbol2scm ("ignore")))
439 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
440 e->name ().c_str ()));
444 A callback that will be chained together with the original cross-staff
445 value of a grob that is placed 'outside or 'around a slur. This just says
446 that any grob becomes cross-staff if it is placed 'outside or 'around a
449 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
451 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
453 if (to_boolean (previous))
456 Grob *me = unsmob<Grob> (smob);
457 Grob *slur = unsmob<Grob> (me->get_object ("slur"));
461 return slur->get_property ("cross-staff");
464 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
466 Slur::calc_cross_staff (SCM smob)
468 Grob *me = unsmob<Grob> (smob);
470 extract_grob_set (me, "note-columns", cols);
471 extract_grob_set (me, "encompass-objects", extras);
473 for (vsize i = 0; i < cols.size (); i++)
475 if (Grob *s = Note_column::get_stem (cols[i]))
476 if (to_boolean (s->get_property ("cross-staff")))
480 /* the separation items are dealt with in replace_breakable_encompass_objects
481 so we can ignore them here */
482 vector<Grob *> non_sep_extras;
483 for (vsize i = 0; i < extras.size (); i++)
484 if (!has_interface<Separation_item> (extras[i]))
485 non_sep_extras.push_back (extras[i]);
487 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
488 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
490 return scm_from_bool (common != me->get_parent (Y_AXIS));
496 "The following properties may be set in the @code{details}"
500 "@item region-size\n"
501 "Size of region (in staff spaces) for determining"
502 " potential endpoints in the Y direction.\n"
503 "@item head-encompass-penalty\n"
504 "Demerit to apply when note heads collide with a slur.\n"
505 "@item stem-encompass-penalty\n"
506 "Demerit to apply when stems collide with a slur.\n"
507 "@item edge-attraction-factor\n"
508 "Factor used to calculate the demerit for distances"
509 " between slur endpoints and their corresponding base"
511 "@item same-slope-penalty\n"
512 "Demerit for slurs with attachment points that are"
513 " horizontally aligned.\n"
514 "@item steeper-slope-factor\n"
515 "Factor used to calculate demerit only if this slur is"
517 "@item non-horizontal-penalty\n"
518 "Demerit for slurs with attachment points that are not"
519 " horizontally aligned.\n"
521 "The maximum slope allowed for this slur.\n"
522 "@item max-slope-factor\n"
523 "Factor that calculates demerit based on the max slope.\n"
524 "@item free-head-distance\n"
525 "The amount of vertical free space that must exist"
526 " between a slur and note heads.\n"
527 "@item absolute-closeness-measure\n"
528 "Factor to calculate demerit for variance between a note"
530 "@item extra-object-collision-penalty\n"
531 "Factor to calculate demerit for extra objects that the"
532 " slur encompasses, including accidentals, fingerings, and"
534 "@item accidental-collision\n"
535 "Factor to calculate demerit for @code{Accidental} objects"
536 " that the slur encompasses. This property value replaces"
537 " the value of @code{extra-object-collision-penalty}.\n"
538 "@item extra-encompass-free-distance\n"
539 "The amount of vertical free space that must exist"
540 " between a slur and various objects it encompasses,"
541 " including accidentals, fingerings, and tuplet numbers.\n"
542 "@item extra-encompass-collision-distance\n"
543 "This detail is currently unused.\n"
544 "@item head-slur-distance-factor\n"
545 "Factor to calculate demerit for variance between a note"
547 "@item head-slur-distance-max-ratio\n"
548 "The maximum value for the ratio of distance between a"
549 " note head and slur.\n"
550 "@item gap-to-staffline-inside\n"
551 "Minimum gap inside the curve of the slur"
552 " where the slur is parallel to a staffline.\n"
553 "@item gap-to-staffline-outside\n"
554 "Minimum gap outside the curve of the slur"
555 " where the slur is parallel to a staffline.\n"
556 "@item free-slur-distance\n"
557 "The amount of vertical free space that must exist"
558 " between adjacent slurs. This subproperty only works"
559 " for @code{PhrasingSlur}.\n"
560 "@item edge-slope-exponent\n"
561 "Factor used to calculate the demerit for the slope of"
562 " a slur near its endpoints; a larger value yields a"
568 "avoid-slur " /* UGH. */