Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / lily / slur.cc
blob433406c92a57d9471daab3524cf5c32091af7ebe
1 /*
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/>.
21 #include "slur.hh"
22 #include "grob-info.hh"
23 #include "grob-array.hh"
24 #include "beam.hh"
25 #include "bezier.hh"
26 #include "directional-element-interface.hh"
27 #include "font-interface.hh"
28 #include "item.hh"
29 #include "pointer-group-interface.hh"
30 #include "lookup.hh"
31 #include "main.hh" // DEBUG_SLUR_SCORING
32 #include "note-column.hh"
33 #include "output-def.hh"
34 #include "spanner.hh"
35 #include "staff-symbol-referencer.hh"
36 #include "stem.hh"
37 #include "text-interface.hh"
38 #include "tie.hh"
39 #include "warn.hh"
40 #include "slur-scoring.hh"
41 #include "separation-item.hh"
42 #include "international.hh"
46 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
47 SCM
48 Slur::calc_direction (SCM smob)
50 Grob *me = unsmob_grob (smob);
51 extract_grob_set (me, "note-columns", encompasses);
53 if (encompasses.empty ())
55 me->suicide ();
56 return SCM_BOOL_F;
59 Direction d = DOWN;
60 for (vsize i = 0; i < encompasses.size (); i++)
62 if (Note_column::dir (encompasses[i]) < 0)
64 d = UP;
65 break;
68 return scm_from_int (d);
71 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
72 SCM
73 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
75 Grob *me = unsmob_grob (smob);
76 int start = scm_to_int (start_scm);
77 int end = scm_to_int (end_scm);
78 Real height = robust_scm2double (me->get_property ("height-limit"), 2.0);
80 extract_grob_set (me, "note-columns", encompasses);
81 Interval ret;
83 Grob *parent = me->get_parent (Y_AXIS);
84 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
85 /* this could happen if, for example, we are a cross-staff slur.
86 in this case, we want to be ignored */
87 return ly_interval2scm (Interval ());
89 for (vsize i = 0; i < encompasses.size (); i++)
91 Interval d = encompasses[i]->pure_height (parent, start, end);
92 if (!d.is_empty ())
93 ret.unite (d);
96 // The +0.5 comes from the fact that we try to place a slur
97 // 0.5 staff spaces from the note-head.
98 // (see Slur_score_state.get_base_attachments ())
99 ret.widen (height * 0.5 + 0.5);
100 return ly_interval2scm (ret);
103 MAKE_SCHEME_CALLBACK (Slur, height, 1);
105 Slur::height (SCM smob)
107 Grob *me = unsmob_grob (smob);
109 // FIXME uncached
110 Stencil *m = me->get_stencil ();
111 return m ? ly_interval2scm (m->extent (Y_AXIS))
112 : ly_interval2scm (Interval ());
115 MAKE_SCHEME_CALLBACK (Slur, print, 1);
117 Slur::print (SCM smob)
119 Grob *me = unsmob_grob (smob);
120 extract_grob_set (me, "note-columns", encompasses);
121 if (encompasses.empty ())
123 me->suicide ();
124 return SCM_EOL;
127 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
128 Real base_thick = staff_thick
129 * robust_scm2double (me->get_property ("thickness"), 1);
130 Real line_thick = staff_thick
131 * robust_scm2double (me->get_property ("line-thickness"), 1);
133 Bezier one = get_curve (me);
134 Stencil a;
136 SCM dash_definition = me->get_property ("dash-definition");
137 a = Lookup::slur (one,
138 get_grob_direction (me) * base_thick,
139 line_thick,
140 dash_definition);
142 #if DEBUG_SLUR_SCORING
143 SCM annotation = me->get_property ("annotation");
144 if (scm_is_string (annotation))
146 string str;
147 SCM properties = Font_interface::text_font_alist_chain (me);
149 if (!scm_is_number (me->get_property ("font-size")))
150 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
151 properties);
153 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
154 (me->layout ()->self_scm (), properties,
155 annotation));
156 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
158 #endif
160 return a.smobbed_copy ();
165 it would be better to do this at engraver level, but that is
166 fragile, as the breakable items are generated on staff level, at
167 which point slur starts and ends have to be tracked
169 void
170 Slur::replace_breakable_encompass_objects (Grob *me)
172 extract_grob_set (me, "encompass-objects", extra_objects);
173 vector<Grob *> new_encompasses;
175 for (vsize i = 0; i < extra_objects.size (); i++)
177 Grob *g = extra_objects[i];
179 if (Separation_item::has_interface (g))
181 extract_grob_set (g, "elements", breakables);
182 for (vsize j = 0; j < breakables.size (); j++)
183 /* if we encompass a separation-item that spans multiple staves,
184 we filter out the grobs that don't belong to our staff */
185 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
186 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
187 new_encompasses.push_back (breakables[j]);
189 else
190 new_encompasses.push_back (g);
193 SCM encompass_scm = me->get_object ("encompass-objects");
194 if (Grob_array::unsmob (encompass_scm))
196 vector<Grob *> &arr =
197 unsmob_grob_array (encompass_scm)->array_reference ();
198 arr = new_encompasses;
202 Bezier
203 Slur::get_curve (Grob *me)
205 Bezier b;
206 int i = 0;
207 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
208 s = scm_cdr (s))
209 b.control_[i++] = ly_scm2offset (scm_car (s));
211 return b;
214 void
215 Slur::add_column (Grob *me, Grob *n)
217 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
218 add_bound_item (dynamic_cast<Spanner *> (me), n);
221 void
222 Slur::add_extra_encompass (Grob *me, Grob *n)
224 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
227 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
229 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
231 int start = robust_scm2int (start_scm, 0);
232 int end = robust_scm2int (end_scm, 0);
233 Grob *script = unsmob_grob (grob);
234 Grob *slur = unsmob_grob (script->get_object ("slur"));
235 if (!slur)
236 return offset_scm;
238 SCM avoid = script->get_property ("avoid-slur");
239 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
240 return offset_scm;
242 Real offset = robust_scm2double (offset_scm, 0.0);
243 Direction dir = get_grob_direction (script);
244 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
247 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
249 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
251 Grob *script = unsmob_grob (grob);
252 Grob *slur = unsmob_grob (script->get_object ("slur"));
254 if (!slur)
255 return offset_scm;
257 SCM avoid = script->get_property ("avoid-slur");
258 if (avoid != ly_symbol2scm ("outside")
259 && avoid != ly_symbol2scm ("around"))
260 return offset_scm;
262 Direction dir = get_grob_direction (script);
263 if (dir == CENTER)
264 return offset_scm;
266 Grob *cx = script->common_refpoint (slur, X_AXIS);
267 Grob *cy = script->common_refpoint (slur, Y_AXIS);
269 Bezier curve = Slur::get_curve (slur);
271 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
272 slur->relative_coordinate (cy, Y_AXIS)));
274 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
275 Interval xext = robust_relative_extent (script, cx, X_AXIS);
277 Real offset = robust_scm2double (offset_scm, 0);
278 yext.translate (offset);
280 /* FIXME: slur property, script property? */
281 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
282 0.0);
283 yext.widen (slur_padding);
285 const Real EPS = 1e-3;
286 Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
287 bool consider[] = {false, false, false};
288 Real ys[] = {0, 0, 0};
289 bool do_shift = false;
291 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
293 Real x = xext.linear_combination ((Direction) d);
294 consider[k] = bezext.contains (x);
296 if (consider[k])
298 ys[k]
299 = (fabs (bezext[LEFT] - x) < EPS)
300 ? curve.control_[0][Y_AXIS]
301 : ((fabs (bezext[RIGHT] - x) < EPS)
302 ? curve.control_[3][Y_AXIS]
303 : curve.get_other_coordinate (X_AXIS, x));
305 /* Request shift if slur is contained script's Y, or if
306 script is inside slur and avoid == outside. */
307 if (yext.contains (ys[k])
308 || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
309 do_shift = true;
313 Real avoidance_offset = 0.0;
314 if (do_shift)
316 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
317 if (consider[k])
318 avoidance_offset = dir * (max (dir * avoidance_offset,
319 dir * (ys[k] - yext[-dir] + dir * slur_padding)));
321 return scm_from_double (offset + avoidance_offset);
325 * Used by Slur_engraver:: and Phrasing_slur_engraver::
327 void
328 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
329 vector<Grob*> &slurs,
330 vector<Grob*> &end_slurs)
332 if (slurs.empty () && end_slurs.empty ())
333 return;
335 Grob *e = info.grob ();
336 SCM avoid = e->get_property ("avoid-slur");
337 if (Tie::has_interface (e)
338 || avoid == ly_symbol2scm ("inside"))
340 for (vsize i = slurs.size (); i--;)
341 add_extra_encompass (slurs[i], e);
342 for (vsize i = end_slurs.size (); i--;)
343 add_extra_encompass (end_slurs[i], e);
345 else if (avoid == ly_symbol2scm ("outside")
346 || avoid == ly_symbol2scm ("around"))
348 Grob *slur;
349 if (end_slurs.size () && !slurs.size ())
350 slur = end_slurs[0];
351 else
352 slur = slurs[0];
354 if (slur)
356 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
357 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
358 e->set_object ("slur", slur->self_scm ());
361 else if (avoid != ly_symbol2scm ("ignore"))
362 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
363 e->name().c_str ()));
367 A callback that will be chained together with the original cross-staff
368 value of a grob that is placed 'outside or 'around a slur. This just says
369 that any grob becomes cross-staff if it is placed 'outside or 'around a
370 cross-staff slur.
372 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
374 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
376 if (previous == SCM_BOOL_T)
377 return previous;
379 Grob *me = unsmob_grob (smob);
380 Grob *slur = unsmob_grob (me->get_object ("slur"));
382 if (!slur)
383 return SCM_BOOL_F;
384 return slur->get_property ("cross-staff");
387 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
389 Slur::calc_cross_staff (SCM smob)
391 Grob *me = unsmob_grob (smob);
393 extract_grob_set (me, "note-columns", cols);
394 extract_grob_set (me, "encompass-objects", extras);
396 for (vsize i = 0; i < cols.size (); i++)
398 if (Grob *s = Note_column::get_stem (cols[i]))
399 if (to_boolean (s->get_property ("cross-staff")))
400 return SCM_BOOL_T;
403 /* the separation items are dealt with in replace_breakable_encompass_objects
404 so we can ignore them here */
405 vector<Grob*> non_sep_extras;
406 for (vsize i = 0; i < extras.size (); i++)
407 if (!Separation_item::has_interface (extras[i]))
408 non_sep_extras.push_back (extras[i]);
410 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
411 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
413 return scm_from_bool (common != me->get_parent (Y_AXIS));
416 ADD_INTERFACE (Slur,
417 "A slur."
418 "\n"
419 "The following properties may be set in the @code{details}"
420 " list.\n"
421 "\n"
422 "@table @code\n"
423 "@item region-size\n"
424 "Size of region (in staff spaces) for determining"
425 " potential endpoints in the Y direction.\n"
426 "@item head-encompass-penalty\n"
427 "Demerit to apply when note heads collide with a slur.\n"
428 "@item stem-encompass-penalty\n"
429 "Demerit to apply when stems collide with a slur.\n"
430 "@item closeness-factor\n"
431 "Additional demerit used when scoring encompasses.\n"
432 "@item edge-attraction-factor\n"
433 "Factor used to calculate the demerit for distances"
434 " between slur endpoints and their corresponding base"
435 " attachments.\n"
436 "@item same-slope-penalty\n"
437 "Demerit for slurs with attachment points that are"
438 " horizontally aligned.\n"
439 "@item steeper-slope-factor\n"
440 "Factor used to calculate demerit only if this slur is"
441 " not broken.\n"
442 "@item non-horizontal-penalty\n"
443 "Demerit for slurs with attachment points that are not"
444 " horizontally aligned.\n"
445 "@item max-slope\n"
446 "The maximum slope allowed for this slur.\n"
447 "@item max-slope-factor\n"
448 "Factor that calculates demerit based on the max slope.\n"
449 "@item free-head-distance\n"
450 "The amount of vertical free space that must exist"
451 " between a slur and note heads.\n"
452 "@item absolute-closeness-measure\n"
453 "Factor to calculate demerit for variance between a note"
454 " head and slur.\n"
455 "@item extra-object-collision-penalty\n"
456 "Factor to calculate demerit for extra objects that the"
457 " slur encompasses, including accidentals, fingerings, and"
458 " tuplet numbers.\n"
459 "@item accidental-collision\n"
460 "Factor to calculate demerit for @code{Accidental} objects"
461 " that the slur encompasses. This property value replaces"
462 " the value of @code{extra-object-collision-penalty}.\n"
463 "@item extra-encompass-free-distance\n"
464 "The amount of vertical free space that must exist"
465 " between a slur and various objects it encompasses,"
466 " including accidentals, fingerings, and tuplet numbers.\n"
467 "@item extra-encompass-collision-distance\n"
468 "This detail is currently unused.\n"
469 "@item head-slur-distance-factor\n"
470 "Factor to calculate demerit for variance between a note"
471 " head and slur.\n"
472 "@item head-slur-distance-max-ratio\n"
473 "The maximum value for the ratio of distance between a"
474 " note head and slur.\n"
475 "@item free-slur-distance\n"
476 "The amount of vertical free space that must exist"
477 " between adjacent slurs. This subproperty only works"
478 " for @code{PhrasingSlur}.\n"
479 "@item edge-slope-exponent\n"
480 "Factor used to calculate the demerit for the slope of"
481 " a slur near its endpoints; a larger value yields a"
482 " larger demerit.\n"
483 "@end table\n",
485 /* properties */
486 "annotation "
487 "avoid-slur " /* UGH. */
488 "control-points "
489 "dash-definition "
490 "details "
491 "direction "
492 "eccentricity "
493 "encompass-objects "
494 "height-limit "
495 "inspect-quants "
496 "inspect-index "
497 "line-thickness "
498 "note-columns "
499 "positions "
500 "ratio "
501 "thickness "