2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2011 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"
35 #include "staff-symbol-referencer.hh"
37 #include "text-interface.hh"
40 #include "slur-scoring.hh"
41 #include "separation-item.hh"
42 #include "international.hh"
44 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
46 Slur::calc_direction (SCM smob)
48 Grob *me = unsmob_grob (smob);
49 extract_grob_set (me, "note-columns", encompasses);
51 if (encompasses.empty ())
58 for (vsize i = 0; i < encompasses.size (); i++)
60 if (Note_column::dir (encompasses[i]) < 0)
66 return scm_from_int (d);
69 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
71 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
73 Grob *me = unsmob_grob (smob);
74 int start = scm_to_int (start_scm);
75 int end = scm_to_int (end_scm);
76 Real height = robust_scm2double (me->get_property ("height-limit"), 2.0);
78 extract_grob_set (me, "note-columns", encompasses);
81 Grob *parent = me->get_parent (Y_AXIS);
82 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
83 /* this could happen if, for example, we are a cross-staff slur.
84 in this case, we want to be ignored */
85 return ly_interval2scm (Interval ());
87 for (vsize i = 0; i < encompasses.size (); i++)
89 Interval d = encompasses[i]->pure_height (parent, start, end);
94 // The +0.5 comes from the fact that we try to place a slur
95 // 0.5 staff spaces from the note-head.
96 // (see Slur_score_state.get_base_attachments ())
97 ret.widen (height * 0.5 + 0.5);
98 return ly_interval2scm (ret);
101 MAKE_SCHEME_CALLBACK (Slur, height, 1);
103 Slur::height (SCM smob)
105 Grob *me = unsmob_grob (smob);
108 Stencil *m = me->get_stencil ();
109 return m ? ly_interval2scm (m->extent (Y_AXIS))
110 : ly_interval2scm (Interval ());
113 MAKE_SCHEME_CALLBACK (Slur, print, 1);
115 Slur::print (SCM smob)
117 Grob *me = unsmob_grob (smob);
118 extract_grob_set (me, "note-columns", encompasses);
119 if (encompasses.empty ())
125 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
126 Real base_thick = staff_thick
127 * robust_scm2double (me->get_property ("thickness"), 1);
128 Real line_thick = staff_thick
129 * robust_scm2double (me->get_property ("line-thickness"), 1);
131 Bezier one = get_curve (me);
134 SCM dash_definition = me->get_property ("dash-definition");
135 a = Lookup::slur (one,
136 get_grob_direction (me) * base_thick,
140 #if DEBUG_SLUR_SCORING
141 SCM annotation = me->get_property ("annotation");
142 if (scm_is_string (annotation))
145 SCM properties = Font_interface::text_font_alist_chain (me);
147 if (!scm_is_number (me->get_property ("font-size")))
148 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
151 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
152 (me->layout ()->self_scm (), properties,
154 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
158 return a.smobbed_copy ();
162 it would be better to do this at engraver level, but that is
163 fragile, as the breakable items are generated on staff level, at
164 which point slur starts and ends have to be tracked
167 Slur::replace_breakable_encompass_objects (Grob *me)
169 extract_grob_set (me, "encompass-objects", extra_objects);
170 vector<Grob *> new_encompasses;
172 for (vsize i = 0; i < extra_objects.size (); i++)
174 Grob *g = extra_objects[i];
176 if (Separation_item::has_interface (g))
178 extract_grob_set (g, "elements", breakables);
179 for (vsize j = 0; j < breakables.size (); j++)
180 /* if we encompass a separation-item that spans multiple staves,
181 we filter out the grobs that don't belong to our staff */
182 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
183 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
184 new_encompasses.push_back (breakables[j]);
187 new_encompasses.push_back (g);
190 SCM encompass_scm = me->get_object ("encompass-objects");
191 if (Grob_array::unsmob (encompass_scm))
194 = unsmob_grob_array (encompass_scm)->array_reference ();
195 arr = new_encompasses;
200 Slur::get_curve (Grob *me)
204 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
206 b.control_[i++] = ly_scm2offset (scm_car (s));
212 Slur::add_column (Grob *me, Grob *n)
214 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
215 add_bound_item (dynamic_cast<Spanner *> (me), n);
219 Slur::add_extra_encompass (Grob *me, Grob *n)
221 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
224 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
226 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
228 int start = robust_scm2int (start_scm, 0);
229 int end = robust_scm2int (end_scm, 0);
230 Grob *script = unsmob_grob (grob);
231 Grob *slur = unsmob_grob (script->get_object ("slur"));
235 SCM avoid = script->get_property ("avoid-slur");
236 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
239 Real offset = robust_scm2double (offset_scm, 0.0);
240 Direction dir = get_grob_direction (script);
241 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
244 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
246 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
248 Grob *script = unsmob_grob (grob);
249 Grob *slur = unsmob_grob (script->get_object ("slur"));
254 SCM avoid = script->get_property ("avoid-slur");
255 if (avoid != ly_symbol2scm ("outside")
256 && avoid != ly_symbol2scm ("around"))
259 Direction dir = get_grob_direction (script);
263 Grob *cx = script->common_refpoint (slur, X_AXIS);
264 Grob *cy = script->common_refpoint (slur, Y_AXIS);
266 Bezier curve = Slur::get_curve (slur);
268 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
269 slur->relative_coordinate (cy, Y_AXIS)));
271 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
272 Interval xext = robust_relative_extent (script, cx, X_AXIS);
273 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
275 bool contains = false;
278 contains |= slur_wid.contains (xext[d]);
279 while (flip (&d) != LEFT);
284 Real offset = robust_scm2double (offset_scm, 0);
285 yext.translate (offset);
287 /* FIXME: slur property, script property? */
288 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
290 yext.widen (slur_padding);
292 Interval exts[] = {xext, yext};
293 bool do_shift = false;
295 if (avoid == ly_symbol2scm ("outside"))
300 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
301 Real y = curve.get_other_coordinate (X_AXIS, x);
302 do_shift = y == minmax (dir, yext[-dir], y);
306 while (flip (&d) != LEFT);
310 for (int a = X_AXIS; a < NO_AXES; a++)
315 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
316 for (vsize i = 0; i < coords.size (); i++)
318 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
325 while (flip (&d) != LEFT);
331 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;
333 return scm_from_double (offset + avoidance_offset);
337 * Used by Slur_engraver:: and Phrasing_slur_engraver::
340 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
341 vector<Grob *> &slurs,
342 vector<Grob *> &end_slurs)
344 if (slurs.empty () && end_slurs.empty ())
347 Grob *e = info.grob ();
348 SCM avoid = e->get_property ("avoid-slur");
349 if (Tie::has_interface (e)
350 || avoid == ly_symbol2scm ("inside"))
352 for (vsize i = slurs.size (); i--;)
353 add_extra_encompass (slurs[i], e);
354 for (vsize i = end_slurs.size (); i--;)
355 add_extra_encompass (end_slurs[i], e);
357 else if (avoid == ly_symbol2scm ("outside")
358 || avoid == ly_symbol2scm ("around"))
361 if (end_slurs.size () && !slurs.size ())
368 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
369 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
370 e->set_object ("slur", slur->self_scm ());
373 else if (avoid != ly_symbol2scm ("ignore"))
374 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
375 e->name ().c_str ()));
379 A callback that will be chained together with the original cross-staff
380 value of a grob that is placed 'outside or 'around a slur. This just says
381 that any grob becomes cross-staff if it is placed 'outside or 'around a
384 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
386 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
388 if (previous == SCM_BOOL_T)
391 Grob *me = unsmob_grob (smob);
392 Grob *slur = unsmob_grob (me->get_object ("slur"));
396 return slur->get_property ("cross-staff");
399 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
401 Slur::calc_cross_staff (SCM smob)
403 Grob *me = unsmob_grob (smob);
405 extract_grob_set (me, "note-columns", cols);
406 extract_grob_set (me, "encompass-objects", extras);
408 for (vsize i = 0; i < cols.size (); i++)
410 if (Grob *s = Note_column::get_stem (cols[i]))
411 if (to_boolean (s->get_property ("cross-staff")))
415 /* the separation items are dealt with in replace_breakable_encompass_objects
416 so we can ignore them here */
417 vector<Grob *> non_sep_extras;
418 for (vsize i = 0; i < extras.size (); i++)
419 if (!Separation_item::has_interface (extras[i]))
420 non_sep_extras.push_back (extras[i]);
422 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
423 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
425 return scm_from_bool (common != me->get_parent (Y_AXIS));
431 "The following properties may be set in the @code{details}"
435 "@item region-size\n"
436 "Size of region (in staff spaces) for determining"
437 " potential endpoints in the Y direction.\n"
438 "@item head-encompass-penalty\n"
439 "Demerit to apply when note heads collide with a slur.\n"
440 "@item stem-encompass-penalty\n"
441 "Demerit to apply when stems collide with a slur.\n"
442 "@item closeness-factor\n"
443 "Additional demerit used when scoring encompasses.\n"
444 "@item edge-attraction-factor\n"
445 "Factor used to calculate the demerit for distances"
446 " between slur endpoints and their corresponding base"
448 "@item same-slope-penalty\n"
449 "Demerit for slurs with attachment points that are"
450 " horizontally aligned.\n"
451 "@item steeper-slope-factor\n"
452 "Factor used to calculate demerit only if this slur is"
454 "@item non-horizontal-penalty\n"
455 "Demerit for slurs with attachment points that are not"
456 " horizontally aligned.\n"
458 "The maximum slope allowed for this slur.\n"
459 "@item max-slope-factor\n"
460 "Factor that calculates demerit based on the max slope.\n"
461 "@item free-head-distance\n"
462 "The amount of vertical free space that must exist"
463 " between a slur and note heads.\n"
464 "@item absolute-closeness-measure\n"
465 "Factor to calculate demerit for variance between a note"
467 "@item extra-object-collision-penalty\n"
468 "Factor to calculate demerit for extra objects that the"
469 " slur encompasses, including accidentals, fingerings, and"
471 "@item accidental-collision\n"
472 "Factor to calculate demerit for @code{Accidental} objects"
473 " that the slur encompasses. This property value replaces"
474 " the value of @code{extra-object-collision-penalty}.\n"
475 "@item extra-encompass-free-distance\n"
476 "The amount of vertical free space that must exist"
477 " between a slur and various objects it encompasses,"
478 " including accidentals, fingerings, and tuplet numbers.\n"
479 "@item extra-encompass-collision-distance\n"
480 "This detail is currently unused.\n"
481 "@item head-slur-distance-factor\n"
482 "Factor to calculate demerit for variance between a note"
484 "@item head-slur-distance-max-ratio\n"
485 "The maximum value for the ratio of distance between a"
486 " note head and slur.\n"
487 "@item free-slur-distance\n"
488 "The amount of vertical free space that must exist"
489 " between adjacent slurs. This subproperty only works"
490 " for @code{PhrasingSlur}.\n"
491 "@item edge-slope-exponent\n"
492 "Factor used to calculate the demerit for the slope of"
493 " a slur near its endpoints; a larger value yields a"
499 "avoid-slur " /* UGH. */