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"
35 #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 "international.hh"
45 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
47 Slur::calc_direction (SCM smob)
49 Grob *me = unsmob_grob (smob);
50 extract_grob_set (me, "note-columns", encompasses);
52 if (encompasses.empty ())
59 for (vsize i = 0; i < encompasses.size (); i++)
61 if (Note_column::dir (encompasses[i]) < 0)
67 return scm_from_int (d);
70 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
72 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
75 Note that this estimation uses a rote add-on of 0.5 to the
76 highest encompassed note-head for a slur estimate. This is,
77 in most cases, shorter than the actual slur.
79 Ways to improve this could include:
80 -- adding extra height for scripts that avoid slurs on the inside
81 -- adding extra height for the "bulge" in a slur above a note head
83 Grob *me = unsmob_grob (smob);
84 int start = scm_to_int (start_scm);
85 int end = scm_to_int (end_scm);
86 Direction dir = get_grob_direction (me);
88 extract_grob_set (me, "note-columns", encompasses);
92 Grob *parent = me->get_parent (Y_AXIS);
93 Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
94 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
95 /* this could happen if, for example, we are a cross-staff slur.
96 in this case, we want to be ignored */
97 return ly_interval2scm (Interval ());
99 for (vsize i = 0; i < encompasses.size (); i++)
101 Interval d = encompasses[i]->pure_height (parent, start, end);
104 Direction downup = DOWN;
106 ret.add_point (d[dir]);
107 while (flip (&downup) != DOWN);
109 if (extremal_heights[LEFT] == infinity_f)
110 extremal_heights[LEFT] = d[dir];
111 extremal_heights[RIGHT] = d[dir];
116 return ly_interval2scm (Interval ());
118 Interval extremal_span;
119 extremal_span.set_empty ();
122 extremal_span.add_point (extremal_heights[d]);
123 while (flip (&d) != LEFT);
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 (Separation_item::has_interface (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 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
218 new_encompasses.push_back (breakables[j]);
221 new_encompasses.push_back (g);
224 SCM encompass_scm = me->get_object ("encompass-objects");
225 if (Grob_array::unsmob (encompass_scm))
228 = unsmob_grob_array (encompass_scm)->array_reference ();
229 arr = new_encompasses;
234 Slur::get_curve (Grob *me)
238 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
240 b.control_[i++] = ly_scm2offset (scm_car (s));
246 Slur::add_column (Grob *me, Grob *n)
248 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
249 add_bound_item (dynamic_cast<Spanner *> (me), n);
253 Slur::add_extra_encompass (Grob *me, Grob *n)
255 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
258 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
260 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
262 int start = robust_scm2int (start_scm, 0);
263 int end = robust_scm2int (end_scm, 0);
264 Grob *script = unsmob_grob (grob);
265 Grob *slur = unsmob_grob (script->get_object ("slur"));
269 SCM avoid = script->get_property ("avoid-slur");
270 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
273 Real offset = robust_scm2double (offset_scm, 0.0);
274 Direction dir = get_grob_direction (script);
275 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
278 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
280 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
282 Grob *script = unsmob_grob (grob);
283 Grob *slur = unsmob_grob (script->get_object ("slur"));
288 SCM avoid = script->get_property ("avoid-slur");
289 if (avoid != ly_symbol2scm ("outside")
290 && avoid != ly_symbol2scm ("around"))
293 Direction dir = get_grob_direction (script);
297 Grob *cx = script->common_refpoint (slur, X_AXIS);
298 Grob *cy = script->common_refpoint (slur, Y_AXIS);
300 Bezier curve = Slur::get_curve (slur);
302 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
303 slur->relative_coordinate (cy, Y_AXIS)));
305 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
306 Interval xext = robust_relative_extent (script, cx, X_AXIS);
307 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
310 cannot use is_empty because some 0-extent scripts
311 come up with TabStaffs.
313 if (xext.length () <= 0 || yext.length () <= 0)
316 bool contains = false;
319 contains |= slur_wid.contains (xext[d]);
320 while (flip (&d) != LEFT);
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 (avoid == ly_symbol2scm ("outside"))
341 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
342 Real y = curve.get_other_coordinate (X_AXIS, x);
343 do_shift = y == minmax (dir, yext[-dir], y);
347 while (flip (&d) != LEFT);
351 for (int a = X_AXIS; a < NO_AXES; a++)
356 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
357 for (vsize i = 0; i < coords.size (); i++)
359 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
366 while (flip (&d) != LEFT);
372 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;
374 return scm_from_double (offset + avoidance_offset);
377 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, vertical_skylines, 1, 0, "");
379 Slur::vertical_skylines (SCM smob)
381 Grob *me = unsmob_grob (smob);
385 return Skyline_pair (boxes, 0.0, X_AXIS).smobbed_copy ();
387 Bezier curve = Slur::get_curve (me);
388 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
389 for (vsize i = 0; i < box_count; i++)
392 b.add_point (curve.curve_point (i * 1.0 / box_count));
393 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
397 return Skyline_pair (boxes, 0.0, X_AXIS).smobbed_copy ();
401 * Used by Slur_engraver:: and Phrasing_slur_engraver::
404 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
405 vector<Grob *> &slurs,
406 vector<Grob *> &end_slurs)
408 if (slurs.empty () && end_slurs.empty ())
411 Grob *e = info.grob ();
412 SCM avoid = e->get_property ("avoid-slur");
413 if (Tie::has_interface (e)
414 || avoid == ly_symbol2scm ("inside"))
416 for (vsize i = slurs.size (); i--;)
417 add_extra_encompass (slurs[i], e);
418 for (vsize i = end_slurs.size (); i--;)
419 add_extra_encompass (end_slurs[i], e);
421 else if (avoid == ly_symbol2scm ("outside")
422 || avoid == ly_symbol2scm ("around"))
425 if (end_slurs.size () && !slurs.size ())
432 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
433 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
434 e->set_object ("slur", slur->self_scm ());
437 else if (avoid != ly_symbol2scm ("ignore"))
438 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
439 e->name ().c_str ()));
443 A callback that will be chained together with the original cross-staff
444 value of a grob that is placed 'outside or 'around a slur. This just says
445 that any grob becomes cross-staff if it is placed 'outside or 'around a
448 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
450 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
452 if (previous == SCM_BOOL_T)
455 Grob *me = unsmob_grob (smob);
456 Grob *slur = unsmob_grob (me->get_object ("slur"));
460 return slur->get_property ("cross-staff");
463 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
465 Slur::calc_cross_staff (SCM smob)
467 Grob *me = unsmob_grob (smob);
469 extract_grob_set (me, "note-columns", cols);
470 extract_grob_set (me, "encompass-objects", extras);
472 for (vsize i = 0; i < cols.size (); i++)
474 if (Grob *s = Note_column::get_stem (cols[i]))
475 if (to_boolean (s->get_property ("cross-staff")))
479 /* the separation items are dealt with in replace_breakable_encompass_objects
480 so we can ignore them here */
481 vector<Grob *> non_sep_extras;
482 for (vsize i = 0; i < extras.size (); i++)
483 if (!Separation_item::has_interface (extras[i]))
484 non_sep_extras.push_back (extras[i]);
486 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
487 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
489 return scm_from_bool (common != me->get_parent (Y_AXIS));
495 "The following properties may be set in the @code{details}"
499 "@item region-size\n"
500 "Size of region (in staff spaces) for determining"
501 " potential endpoints in the Y direction.\n"
502 "@item head-encompass-penalty\n"
503 "Demerit to apply when note heads collide with a slur.\n"
504 "@item stem-encompass-penalty\n"
505 "Demerit to apply when stems collide with a slur.\n"
506 "@item edge-attraction-factor\n"
507 "Factor used to calculate the demerit for distances"
508 " between slur endpoints and their corresponding base"
510 "@item same-slope-penalty\n"
511 "Demerit for slurs with attachment points that are"
512 " horizontally aligned.\n"
513 "@item steeper-slope-factor\n"
514 "Factor used to calculate demerit only if this slur is"
516 "@item non-horizontal-penalty\n"
517 "Demerit for slurs with attachment points that are not"
518 " horizontally aligned.\n"
520 "The maximum slope allowed for this slur.\n"
521 "@item max-slope-factor\n"
522 "Factor that calculates demerit based on the max slope.\n"
523 "@item free-head-distance\n"
524 "The amount of vertical free space that must exist"
525 " between a slur and note heads.\n"
526 "@item absolute-closeness-measure\n"
527 "Factor to calculate demerit for variance between a note"
529 "@item extra-object-collision-penalty\n"
530 "Factor to calculate demerit for extra objects that the"
531 " slur encompasses, including accidentals, fingerings, and"
533 "@item accidental-collision\n"
534 "Factor to calculate demerit for @code{Accidental} objects"
535 " that the slur encompasses. This property value replaces"
536 " the value of @code{extra-object-collision-penalty}.\n"
537 "@item extra-encompass-free-distance\n"
538 "The amount of vertical free space that must exist"
539 " between a slur and various objects it encompasses,"
540 " including accidentals, fingerings, and tuplet numbers.\n"
541 "@item extra-encompass-collision-distance\n"
542 "This detail is currently unused.\n"
543 "@item head-slur-distance-factor\n"
544 "Factor to calculate demerit for variance between a note"
546 "@item head-slur-distance-max-ratio\n"
547 "The maximum value for the ratio of distance between a"
548 " note head and slur.\n"
549 "@item free-slur-distance\n"
550 "The amount of vertical free space that must exist"
551 " between adjacent slurs. This subproperty only works"
552 " for @code{PhrasingSlur}.\n"
553 "@item edge-slope-exponent\n"
554 "Factor used to calculate the demerit for the slope of"
555 " a slur near its endpoints; a larger value yields a"
561 "avoid-slur " /* UGH. */
573 "skyline-quantizing "