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)
74 Note that this estimation uses a rote add-on of 0.5 to the
75 highest encompassed note-head for a slur estimate. This is,
76 in most cases, shorter than the actual slur.
78 Ways to improve this could include:
79 -- adding extra height for scripts that avoid slurs on the inside
80 -- adding extra height for the "bulge" in a slur above a note head
82 Grob *me = unsmob_grob (smob);
83 int start = scm_to_int (start_scm);
84 int end = scm_to_int (end_scm);
85 Direction dir = get_grob_direction (me);
87 extract_grob_set (me, "note-columns", encompasses);
91 Grob *parent = me->get_parent (Y_AXIS);
92 Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
93 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
94 /* this could happen if, for example, we are a cross-staff slur.
95 in this case, we want to be ignored */
96 return ly_interval2scm (Interval ());
98 for (vsize i = 0; i < encompasses.size (); i++)
100 Interval d = encompasses[i]->pure_height (parent, start, end);
103 Direction downup = DOWN;
105 ret.add_point (d[dir]);
106 while (flip (&downup) != DOWN);
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 ();
121 extremal_span.add_point (extremal_heights[d]);
122 while (flip (&d) != LEFT);
123 ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
126 The +0.5 comes from the fact that we try to place a slur
127 0.5 staff spaces from the note-head.
128 (see Slur_score_state.get_base_attachments ())
131 return ly_interval2scm (ret);
134 MAKE_SCHEME_CALLBACK (Slur, height, 1);
136 Slur::height (SCM smob)
138 Grob *me = unsmob_grob (smob);
141 Stencil *m = me->get_stencil ();
142 return m ? ly_interval2scm (m->extent (Y_AXIS))
143 : ly_interval2scm (Interval ());
146 MAKE_SCHEME_CALLBACK (Slur, print, 1);
148 Slur::print (SCM smob)
150 Grob *me = unsmob_grob (smob);
151 extract_grob_set (me, "note-columns", encompasses);
152 if (encompasses.empty ())
158 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
159 Real base_thick = staff_thick
160 * robust_scm2double (me->get_property ("thickness"), 1);
161 Real line_thick = staff_thick
162 * robust_scm2double (me->get_property ("line-thickness"), 1);
164 Bezier one = get_curve (me);
167 SCM dash_definition = me->get_property ("dash-definition");
168 a = Lookup::slur (one,
169 get_grob_direction (me) * base_thick,
173 #if DEBUG_SLUR_SCORING
174 SCM annotation = me->get_property ("annotation");
175 if (scm_is_string (annotation))
178 SCM properties = Font_interface::text_font_alist_chain (me);
180 if (!scm_is_number (me->get_property ("font-size")))
181 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
184 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
185 (me->layout ()->self_scm (), properties,
187 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
191 return a.smobbed_copy ();
195 it would be better to do this at engraver level, but that is
196 fragile, as the breakable items are generated on staff level, at
197 which point slur starts and ends have to be tracked
200 Slur::replace_breakable_encompass_objects (Grob *me)
202 extract_grob_set (me, "encompass-objects", extra_objects);
203 vector<Grob *> new_encompasses;
205 for (vsize i = 0; i < extra_objects.size (); i++)
207 Grob *g = extra_objects[i];
209 if (Separation_item::has_interface (g))
211 extract_grob_set (g, "elements", breakables);
212 for (vsize j = 0; j < breakables.size (); j++)
213 /* if we encompass a separation-item that spans multiple staves,
214 we filter out the grobs that don't belong to our staff */
215 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
216 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
217 new_encompasses.push_back (breakables[j]);
220 new_encompasses.push_back (g);
223 SCM encompass_scm = me->get_object ("encompass-objects");
224 if (Grob_array::unsmob (encompass_scm))
227 = unsmob_grob_array (encompass_scm)->array_reference ();
228 arr = new_encompasses;
233 Slur::get_curve (Grob *me)
237 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
239 b.control_[i++] = ly_scm2offset (scm_car (s));
245 Slur::add_column (Grob *me, Grob *n)
247 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
248 add_bound_item (dynamic_cast<Spanner *> (me), n);
252 Slur::add_extra_encompass (Grob *me, Grob *n)
254 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
257 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
259 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
261 int start = robust_scm2int (start_scm, 0);
262 int end = robust_scm2int (end_scm, 0);
263 Grob *script = unsmob_grob (grob);
264 Grob *slur = unsmob_grob (script->get_object ("slur"));
268 SCM avoid = script->get_property ("avoid-slur");
269 if (avoid != ly_symbol2scm ("outside") && 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 (avoid != ly_symbol2scm ("outside")
289 && 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;
318 contains |= slur_wid.contains (xext[d]);
319 while (flip (&d) != LEFT);
324 Real offset = robust_scm2double (offset_scm, 0);
325 yext.translate (offset);
327 /* FIXME: slur property, script property? */
328 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
330 yext.widen (slur_padding);
332 Interval exts[] = {xext, yext};
333 bool do_shift = false;
335 if (avoid == ly_symbol2scm ("outside"))
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);
346 while (flip (&d) != LEFT);
350 for (int a = X_AXIS; a < NO_AXES; a++)
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]);
365 while (flip (&d) != LEFT);
371 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;
373 return scm_from_double (offset + avoidance_offset);
377 * Used by Slur_engraver:: and Phrasing_slur_engraver::
380 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
381 vector<Grob *> &slurs,
382 vector<Grob *> &end_slurs)
384 if (slurs.empty () && end_slurs.empty ())
387 Grob *e = info.grob ();
388 SCM avoid = e->get_property ("avoid-slur");
389 if (Tie::has_interface (e)
390 || avoid == ly_symbol2scm ("inside"))
392 for (vsize i = slurs.size (); i--;)
393 add_extra_encompass (slurs[i], e);
394 for (vsize i = end_slurs.size (); i--;)
395 add_extra_encompass (end_slurs[i], e);
397 else if (avoid == ly_symbol2scm ("outside")
398 || avoid == ly_symbol2scm ("around"))
401 if (end_slurs.size () && !slurs.size ())
408 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
409 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
410 e->set_object ("slur", slur->self_scm ());
413 else if (avoid != ly_symbol2scm ("ignore"))
414 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
415 e->name ().c_str ()));
419 A callback that will be chained together with the original cross-staff
420 value of a grob that is placed 'outside or 'around a slur. This just says
421 that any grob becomes cross-staff if it is placed 'outside or 'around a
424 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
426 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
428 if (previous == SCM_BOOL_T)
431 Grob *me = unsmob_grob (smob);
432 Grob *slur = unsmob_grob (me->get_object ("slur"));
436 return slur->get_property ("cross-staff");
439 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
441 Slur::calc_cross_staff (SCM smob)
443 Grob *me = unsmob_grob (smob);
445 extract_grob_set (me, "note-columns", cols);
446 extract_grob_set (me, "encompass-objects", extras);
448 for (vsize i = 0; i < cols.size (); i++)
450 if (Grob *s = Note_column::get_stem (cols[i]))
451 if (to_boolean (s->get_property ("cross-staff")))
455 /* the separation items are dealt with in replace_breakable_encompass_objects
456 so we can ignore them here */
457 vector<Grob *> non_sep_extras;
458 for (vsize i = 0; i < extras.size (); i++)
459 if (!Separation_item::has_interface (extras[i]))
460 non_sep_extras.push_back (extras[i]);
462 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
463 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
465 return scm_from_bool (common != me->get_parent (Y_AXIS));
471 "The following properties may be set in the @code{details}"
475 "@item region-size\n"
476 "Size of region (in staff spaces) for determining"
477 " potential endpoints in the Y direction.\n"
478 "@item head-encompass-penalty\n"
479 "Demerit to apply when note heads collide with a slur.\n"
480 "@item stem-encompass-penalty\n"
481 "Demerit to apply when stems collide with a slur.\n"
482 "@item edge-attraction-factor\n"
483 "Factor used to calculate the demerit for distances"
484 " between slur endpoints and their corresponding base"
486 "@item same-slope-penalty\n"
487 "Demerit for slurs with attachment points that are"
488 " horizontally aligned.\n"
489 "@item steeper-slope-factor\n"
490 "Factor used to calculate demerit only if this slur is"
492 "@item non-horizontal-penalty\n"
493 "Demerit for slurs with attachment points that are not"
494 " horizontally aligned.\n"
496 "The maximum slope allowed for this slur.\n"
497 "@item max-slope-factor\n"
498 "Factor that calculates demerit based on the max slope.\n"
499 "@item free-head-distance\n"
500 "The amount of vertical free space that must exist"
501 " between a slur and note heads.\n"
502 "@item absolute-closeness-measure\n"
503 "Factor to calculate demerit for variance between a note"
505 "@item extra-object-collision-penalty\n"
506 "Factor to calculate demerit for extra objects that the"
507 " slur encompasses, including accidentals, fingerings, and"
509 "@item accidental-collision\n"
510 "Factor to calculate demerit for @code{Accidental} objects"
511 " that the slur encompasses. This property value replaces"
512 " the value of @code{extra-object-collision-penalty}.\n"
513 "@item extra-encompass-free-distance\n"
514 "The amount of vertical free space that must exist"
515 " between a slur and various objects it encompasses,"
516 " including accidentals, fingerings, and tuplet numbers.\n"
517 "@item extra-encompass-collision-distance\n"
518 "This detail is currently unused.\n"
519 "@item head-slur-distance-factor\n"
520 "Factor to calculate demerit for variance between a note"
522 "@item head-slur-distance-max-ratio\n"
523 "The maximum value for the ratio of distance between a"
524 " note head and slur.\n"
525 "@item free-slur-distance\n"
526 "The amount of vertical free space that must exist"
527 " between adjacent slurs. This subproperty only works"
528 " for @code{PhrasingSlur}.\n"
529 "@item edge-slope-exponent\n"
530 "Factor used to calculate the demerit for the slope of"
531 " a slur near its endpoints; a larger value yields a"
537 "avoid-slur " /* UGH. */