2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2012 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 vector<Grob *> encompasses;
52 extract_grob_set (me, "note-columns", ro_encompasses);
53 encompasses.insert (encompasses.end (), ro_encompasses.begin (), ro_encompasses.end ());
54 if (Grob *other_half = unsmob_grob (me->get_object ("other-half")))
56 extract_grob_set (other_half, "note-columns", oh_encompasses);
57 encompasses.insert (encompasses.end (), oh_encompasses.begin (), oh_encompasses.end ());
60 if (encompasses.empty ())
67 for (vsize i = 0; i < encompasses.size (); i++)
69 if (Note_column::dir (encompasses[i]) < 0)
75 return scm_from_int (d);
78 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
80 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
83 Note that this estimation uses a rote add-on of 0.5 to the
84 highest encompassed note-head for a slur estimate. This is,
85 in most cases, shorter than the actual slur.
87 Ways to improve this could include:
88 -- adding extra height for scripts that avoid slurs on the inside
89 -- adding extra height for the "bulge" in a slur above a note head
91 Grob *me = unsmob_grob (smob);
92 int start = scm_to_int (start_scm);
93 int end = scm_to_int (end_scm);
94 Direction dir = get_grob_direction (me);
96 extract_grob_set (me, "note-columns", encompasses);
100 Grob *parent = me->get_parent (Y_AXIS);
101 Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
102 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
103 /* this could happen if, for example, we are a cross-staff slur.
104 in this case, we want to be ignored */
105 return ly_interval2scm (Interval ());
107 for (vsize i = 0; i < encompasses.size (); i++)
109 Interval d = encompasses[i]->pure_height (parent, start, end);
112 for (DOWN_and_UP (downup))
113 ret.add_point (d[dir]);
115 if (extremal_heights[LEFT] == infinity_f)
116 extremal_heights[LEFT] = d[dir];
117 extremal_heights[RIGHT] = d[dir];
122 return ly_interval2scm (Interval ());
124 Interval extremal_span;
125 extremal_span.set_empty ();
126 for (LEFT_and_RIGHT (d))
127 extremal_span.add_point (extremal_heights[d]);
128 ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
131 The +0.5 comes from the fact that we try to place a slur
132 0.5 staff spaces from the note-head.
133 (see Slur_score_state.get_base_attachments ())
136 return ly_interval2scm (ret);
139 MAKE_SCHEME_CALLBACK (Slur, height, 1);
141 Slur::height (SCM smob)
143 Grob *me = unsmob_grob (smob);
146 Stencil *m = me->get_stencil ();
147 return m ? ly_interval2scm (m->extent (Y_AXIS))
148 : ly_interval2scm (Interval ());
151 MAKE_SCHEME_CALLBACK (Slur, print, 1);
153 Slur::print (SCM smob)
155 Grob *me = unsmob_grob (smob);
156 extract_grob_set (me, "note-columns", encompasses);
157 if (encompasses.empty ())
163 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
164 Real base_thick = staff_thick
165 * robust_scm2double (me->get_property ("thickness"), 1);
166 Real line_thick = staff_thick
167 * robust_scm2double (me->get_property ("line-thickness"), 1);
169 Bezier one = get_curve (me);
172 SCM dash_definition = me->get_property ("dash-definition");
173 a = Lookup::slur (one,
174 get_grob_direction (me) * base_thick,
178 #if DEBUG_SLUR_SCORING
179 SCM annotation = me->get_property ("annotation");
180 if (scm_is_string (annotation))
183 SCM properties = Font_interface::text_font_alist_chain (me);
185 if (!scm_is_number (me->get_property ("font-size")))
186 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
189 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
190 (me->layout ()->self_scm (), properties,
192 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
196 return a.smobbed_copy ();
200 it would be better to do this at engraver level, but that is
201 fragile, as the breakable items are generated on staff level, at
202 which point slur starts and ends have to be tracked
205 Slur::replace_breakable_encompass_objects (Grob *me)
207 extract_grob_set (me, "encompass-objects", extra_objects);
208 vector<Grob *> new_encompasses;
210 for (vsize i = 0; i < extra_objects.size (); i++)
212 Grob *g = extra_objects[i];
214 if (Separation_item::has_interface (g))
216 extract_grob_set (g, "elements", breakables);
217 for (vsize j = 0; j < breakables.size (); j++)
218 /* if we encompass a separation-item that spans multiple staves,
219 we filter out the grobs that don't belong to our staff */
220 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
221 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
222 new_encompasses.push_back (breakables[j]);
225 new_encompasses.push_back (g);
228 SCM encompass_scm = me->get_object ("encompass-objects");
229 if (Grob_array::unsmob (encompass_scm))
232 = unsmob_grob_array (encompass_scm)->array_reference ();
233 arr = new_encompasses;
238 Slur::get_curve (Grob *me)
242 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
244 b.control_[i++] = ly_scm2offset (scm_car (s));
250 Slur::add_column (Grob *me, Grob *n)
252 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
253 add_bound_item (dynamic_cast<Spanner *> (me), n);
257 Slur::add_extra_encompass (Grob *me, Grob *n)
259 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
262 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
264 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
266 int start = robust_scm2int (start_scm, 0);
267 int end = robust_scm2int (end_scm, 0);
268 Grob *script = unsmob_grob (grob);
269 Grob *slur = unsmob_grob (script->get_object ("slur"));
273 SCM avoid = script->get_property ("avoid-slur");
274 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
277 Real offset = robust_scm2double (offset_scm, 0.0);
278 Direction dir = get_grob_direction (script);
279 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
282 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
284 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
286 Grob *script = unsmob_grob (grob);
287 Grob *slur = unsmob_grob (script->get_object ("slur"));
292 SCM avoid = script->get_property ("avoid-slur");
293 if (avoid != ly_symbol2scm ("outside")
294 && avoid != ly_symbol2scm ("around"))
297 Direction dir = get_grob_direction (script);
301 Grob *cx = script->common_refpoint (slur, X_AXIS);
302 Grob *cy = script->common_refpoint (slur, Y_AXIS);
304 Bezier curve = Slur::get_curve (slur);
306 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
307 slur->relative_coordinate (cy, Y_AXIS)));
309 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
310 Interval xext = robust_relative_extent (script, cx, X_AXIS);
311 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
314 cannot use is_empty because some 0-extent scripts
315 come up with TabStaffs.
317 if (xext.length () <= 0 || yext.length () <= 0)
320 bool contains = false;
321 for (LEFT_and_RIGHT (d))
322 contains |= slur_wid.contains (xext[d]);
327 Real offset = robust_scm2double (offset_scm, 0);
328 yext.translate (offset);
330 /* FIXME: slur property, script property? */
331 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
333 yext.widen (slur_padding);
335 Interval exts[] = {xext, yext};
336 bool do_shift = false;
338 if (avoid == ly_symbol2scm ("outside"))
340 for (LEFT_and_RIGHT (d))
342 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
343 Real y = curve.get_other_coordinate (X_AXIS, x);
344 do_shift = y == minmax (dir, yext[-dir], y);
351 for (int a = X_AXIS; a < NO_AXES; a++)
353 for (LEFT_and_RIGHT (d))
355 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
356 for (vsize i = 0; i < coords.size (); i++)
358 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
370 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;
372 return scm_from_double (offset + avoidance_offset);
375 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
377 Slur::vertical_skylines (SCM smob)
379 Grob *me = unsmob_grob (smob);
383 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
385 Bezier curve = Slur::get_curve (me);
386 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
387 for (vsize i = 0; i < box_count; i++)
390 b.add_point (curve.curve_point (i * 1.0 / box_count));
391 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
395 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
399 * Used by Slur_engraver:: and Phrasing_slur_engraver::
402 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
403 vector<Grob *> &slurs,
404 vector<Grob *> &end_slurs)
406 if (slurs.empty () && end_slurs.empty ())
409 Grob *e = info.grob ();
410 SCM avoid = e->get_property ("avoid-slur");
412 if (end_slurs.size () && !slurs.size ())
417 if (Tie::has_interface (e)
418 || avoid == ly_symbol2scm ("inside"))
420 for (vsize i = slurs.size (); i--;)
421 add_extra_encompass (slurs[i], e);
422 for (vsize i = end_slurs.size (); i--;)
423 add_extra_encompass (end_slurs[i], e);
425 e->set_object ("slur", slur->self_scm ());
427 else if (avoid == ly_symbol2scm ("outside")
428 || avoid == ly_symbol2scm ("around"))
432 chain_offset_callback (e,
433 ly_make_unpure_pure_container (outside_slur_callback_proc,
434 pure_outside_slur_callback_proc),
436 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
437 e->set_object ("slur", slur->self_scm ());
440 else if (avoid != ly_symbol2scm ("ignore"))
441 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
442 e->name ().c_str ()));
446 A callback that will be chained together with the original cross-staff
447 value of a grob that is placed 'outside or 'around a slur. This just says
448 that any grob becomes cross-staff if it is placed 'outside or 'around a
451 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
453 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
455 if (previous == SCM_BOOL_T)
458 Grob *me = unsmob_grob (smob);
459 Grob *slur = unsmob_grob (me->get_object ("slur"));
463 return slur->get_property ("cross-staff");
466 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
468 Slur::calc_cross_staff (SCM smob)
470 Grob *me = unsmob_grob (smob);
472 extract_grob_set (me, "note-columns", cols);
473 extract_grob_set (me, "encompass-objects", extras);
475 for (vsize i = 0; i < cols.size (); i++)
477 if (Grob *s = Note_column::get_stem (cols[i]))
478 if (to_boolean (s->get_property ("cross-staff")))
482 /* the separation items are dealt with in replace_breakable_encompass_objects
483 so we can ignore them here */
484 vector<Grob *> non_sep_extras;
485 for (vsize i = 0; i < extras.size (); i++)
486 if (!Separation_item::has_interface (extras[i]))
487 non_sep_extras.push_back (extras[i]);
489 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
490 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
492 return scm_from_bool (common != me->get_parent (Y_AXIS));
498 "The following properties may be set in the @code{details}"
502 "@item region-size\n"
503 "Size of region (in staff spaces) for determining"
504 " potential endpoints in the Y direction.\n"
505 "@item head-encompass-penalty\n"
506 "Demerit to apply when note heads collide with a slur.\n"
507 "@item stem-encompass-penalty\n"
508 "Demerit to apply when stems collide with a slur.\n"
509 "@item edge-attraction-factor\n"
510 "Factor used to calculate the demerit for distances"
511 " between slur endpoints and their corresponding base"
513 "@item same-slope-penalty\n"
514 "Demerit for slurs with attachment points that are"
515 " horizontally aligned.\n"
516 "@item steeper-slope-factor\n"
517 "Factor used to calculate demerit only if this slur is"
519 "@item non-horizontal-penalty\n"
520 "Demerit for slurs with attachment points that are not"
521 " horizontally aligned.\n"
523 "The maximum slope allowed for this slur.\n"
524 "@item max-slope-factor\n"
525 "Factor that calculates demerit based on the max slope.\n"
526 "@item free-head-distance\n"
527 "The amount of vertical free space that must exist"
528 " between a slur and note heads.\n"
529 "@item absolute-closeness-measure\n"
530 "Factor to calculate demerit for variance between a note"
532 "@item extra-object-collision-penalty\n"
533 "Factor to calculate demerit for extra objects that the"
534 " slur encompasses, including accidentals, fingerings, and"
536 "@item accidental-collision\n"
537 "Factor to calculate demerit for @code{Accidental} objects"
538 " that the slur encompasses. This property value replaces"
539 " the value of @code{extra-object-collision-penalty}.\n"
540 "@item extra-encompass-free-distance\n"
541 "The amount of vertical free space that must exist"
542 " between a slur and various objects it encompasses,"
543 " including accidentals, fingerings, and tuplet numbers.\n"
544 "@item extra-encompass-collision-distance\n"
545 "This detail is currently unused.\n"
546 "@item head-slur-distance-factor\n"
547 "Factor to calculate demerit for variance between a note"
549 "@item head-slur-distance-max-ratio\n"
550 "The maximum value for the ratio of distance between a"
551 " note head and slur.\n"
552 "@item free-slur-distance\n"
553 "The amount of vertical free space that must exist"
554 " between adjacent slurs. This subproperty only works"
555 " for @code{PhrasingSlur}.\n"
556 "@item edge-slope-exponent\n"
557 "Factor used to calculate the demerit for the slope of"
558 " a slur near its endpoints; a larger value yields a"
564 "avoid-slur " /* UGH. */