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 "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 for (DOWN_and_UP (downup))
105 ret.add_point (d[dir]);
107 if (extremal_heights[LEFT] == infinity_f)
108 extremal_heights[LEFT] = d[dir];
109 extremal_heights[RIGHT] = d[dir];
114 return ly_interval2scm (Interval ());
116 Interval extremal_span;
117 extremal_span.set_empty ();
118 for (LEFT_and_RIGHT (d))
119 extremal_span.add_point (extremal_heights[d]);
120 ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
123 The +0.5 comes from the fact that we try to place a slur
124 0.5 staff spaces from the note-head.
125 (see Slur_score_state.get_base_attachments ())
128 return ly_interval2scm (ret);
131 MAKE_SCHEME_CALLBACK (Slur, height, 1);
133 Slur::height (SCM smob)
135 Grob *me = unsmob_grob (smob);
138 Stencil *m = me->get_stencil ();
139 return m ? ly_interval2scm (m->extent (Y_AXIS))
140 : ly_interval2scm (Interval ());
143 MAKE_SCHEME_CALLBACK (Slur, print, 1);
145 Slur::print (SCM smob)
147 Grob *me = unsmob_grob (smob);
149 extract_grob_set (me, "note-columns", encompasses);
150 if (encompasses.empty ())
156 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
157 Real base_thick = staff_thick
158 * robust_scm2double (me->get_property ("thickness"), 1);
159 Real line_thick = staff_thick
160 * robust_scm2double (me->get_property ("line-thickness"), 1);
162 Bezier one = get_curve (me);
165 SCM dash_definition = me->get_property ("dash-definition");
166 a = Lookup::slur (one,
167 get_grob_direction (me) * base_thick,
171 #if DEBUG_SLUR_SCORING
172 SCM annotation = me->get_property ("annotation");
173 if (scm_is_string (annotation))
176 SCM properties = Font_interface::text_font_alist_chain (me);
178 if (!scm_is_number (me->get_property ("font-size")))
179 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
182 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
183 (me->layout ()->self_scm (), properties,
185 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
189 return a.smobbed_copy ();
193 it would be better to do this at engraver level, but that is
194 fragile, as the breakable items are generated on staff level, at
195 which point slur starts and ends have to be tracked
198 Slur::replace_breakable_encompass_objects (Grob *me)
200 extract_grob_set (me, "encompass-objects", extra_objects);
201 vector<Grob *> new_encompasses;
203 for (vsize i = 0; i < extra_objects.size (); i++)
205 Grob *g = extra_objects[i];
207 if (Separation_item::has_interface (g))
209 extract_grob_set (g, "elements", breakables);
210 for (vsize j = 0; j < breakables.size (); j++)
211 /* if we encompass a separation-item that spans multiple staves,
212 we filter out the grobs that don't belong to our staff */
213 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
214 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
215 new_encompasses.push_back (breakables[j]);
218 new_encompasses.push_back (g);
221 SCM encompass_scm = me->get_object ("encompass-objects");
222 if (Grob_array::unsmob (encompass_scm))
225 = unsmob_grob_array (encompass_scm)->array_reference ();
226 arr = new_encompasses;
231 Slur::get_curve (Grob *me)
235 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
237 b.control_[i++] = ly_scm2offset (scm_car (s));
243 Slur::add_column (Grob *me, Grob *n)
245 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
246 add_bound_item (dynamic_cast<Spanner *> (me), n);
250 Slur::add_extra_encompass (Grob *me, Grob *n)
252 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
256 Slur::main_to_stub (Grob *main, Grob *stub)
258 extract_grob_set (main, "note-columns", nc);
259 for (vsize i = 0; i < nc.size (); i++)
260 add_column (stub, nc[i]);
262 extract_grob_set (main, "encompass-objects", eo);
263 for (vsize i = 0; i < eo.size (); i++)
264 add_extra_encompass (stub, eo[i]);
266 stub->set_object ("surrogate", main->self_scm ());
268 dynamic_cast<Spanner *> (stub)->set_bound
269 (LEFT, dynamic_cast<Spanner *> (main)->get_bound (LEFT));
270 dynamic_cast<Spanner *> (stub)->set_bound
271 (RIGHT, dynamic_cast<Spanner *> (main)->get_bound (RIGHT));
274 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
276 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
278 int start = robust_scm2int (start_scm, 0);
279 int end = robust_scm2int (end_scm, 0);
280 Grob *script = unsmob_grob (grob);
281 Grob *slur = unsmob_grob (script->get_object ("slur"));
285 SCM avoid = script->get_property ("avoid-slur");
286 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
289 Real offset = robust_scm2double (offset_scm, 0.0);
290 Direction dir = get_grob_direction (script);
291 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
294 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
296 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
298 Grob *script = unsmob_grob (grob);
299 Grob *slur = unsmob_grob (script->get_object ("slur"));
304 SCM avoid = script->get_property ("avoid-slur");
305 if (avoid != ly_symbol2scm ("outside")
306 && avoid != ly_symbol2scm ("around"))
309 Direction dir = get_grob_direction (script);
313 Grob *cx = script->common_refpoint (slur, X_AXIS);
314 Grob *cy = script->common_refpoint (slur, Y_AXIS);
316 Bezier curve = Slur::get_curve (slur);
318 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
319 slur->relative_coordinate (cy, Y_AXIS)));
321 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
322 Interval xext = robust_relative_extent (script, cx, X_AXIS);
323 Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
326 cannot use is_empty because some 0-extent scripts
327 come up with TabStaffs.
329 if (xext.length () <= 0 || yext.length () <= 0)
332 bool contains = false;
333 for (LEFT_and_RIGHT (d))
334 contains |= slur_wid.contains (xext[d]);
339 Real offset = robust_scm2double (offset_scm, 0);
340 yext.translate (offset);
342 /* FIXME: slur property, script property? */
343 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
345 yext.widen (slur_padding);
347 Interval exts[] = {xext, yext};
348 bool do_shift = false;
350 if (avoid == ly_symbol2scm ("outside"))
352 for (LEFT_and_RIGHT (d))
354 Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
355 Real y = curve.get_other_coordinate (X_AXIS, x);
356 do_shift = y == minmax (dir, yext[-dir], y);
363 for (int a = X_AXIS; a < NO_AXES; a++)
365 for (LEFT_and_RIGHT (d))
367 vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
368 for (vsize i = 0; i < coords.size (); i++)
370 do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
382 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;
384 return scm_from_double (offset + avoidance_offset);
387 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
389 Slur::vertical_skylines (SCM smob)
391 Grob *me = unsmob_grob (smob);
395 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
397 Bezier curve = Slur::get_curve (me);
398 vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
399 for (vsize i = 0; i < box_count; i++)
402 b.add_point (curve.curve_point (i * 1.0 / box_count));
403 b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
407 return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
411 * USE ME ONLY FOR CROSS STAFF SLURS!
412 * We only want to keep the topmost skyline of the topmost axis group(s)
413 * and the bottommost skyline of the bottommost axis group(s). Otherwise,
414 * the VerticalAxisGroups will be spaced very far apart to accommodate the
415 * slur, which we don't want, as it is cross staff.
417 * TODO: Currently, the code below keeps the topmost and bottommost axis
418 * groups and gets rid of the rest. This should be more nuanced for
419 * cases like ossias where the topmost staff changes over the course of
420 * the slur. Ditto for the bottommost staff.
423 MAKE_SCHEME_CALLBACK (Slur, extremal_stub_vertical_skylines, 1);
425 Slur::extremal_stub_vertical_skylines (SCM smob)
427 Grob *me = unsmob_grob (smob);
428 Grob *my_vag = Grob::get_vertical_axis_group (me);
429 extract_grob_set (me, "note-columns", ro_note_columns);
430 vector<Grob *> note_columns (ro_note_columns);
431 vector_sort (note_columns, Grob::vertical_less);
432 bool highest = my_vag == Grob::get_vertical_axis_group (note_columns[0]);
433 bool lowest = my_vag == Grob::get_vertical_axis_group (note_columns.back ());
434 if (!highest && !lowest)
435 return Skyline_pair ().smobbed_copy ();
437 Skyline_pair sky = *Skyline_pair::unsmob (vertical_skylines (smob));
440 sky[DOWN] = Skyline (DOWN);
442 sky[UP] = Skyline (UP);
444 return sky.smobbed_copy ();
448 * Used by Slur_engraver:: and Phrasing_slur_engraver::
451 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
452 vector<Slur_info> &slur_infos,
453 vector<Slur_info> &end_slur_infos)
455 if (slur_infos.empty () && end_slur_infos.empty ())
458 Grob *e = info.grob ();
459 SCM avoid = e->get_property ("avoid-slur");
461 if (end_slur_infos.size () && !slur_infos.size ())
462 slur = end_slur_infos[0].slur_;
464 slur = slur_infos[0].slur_;
466 if (Tie::has_interface (e)
467 || avoid == ly_symbol2scm ("inside"))
469 for (vsize i = slur_infos.size (); i--;)
470 add_extra_encompass (slur_infos[i].slur_, e);
471 for (vsize i = end_slur_infos.size (); i--;)
472 add_extra_encompass (end_slur_infos[i].slur_, e);
474 e->set_object ("slur", slur->self_scm ());
476 else if (avoid == ly_symbol2scm ("outside")
477 || avoid == ly_symbol2scm ("around"))
481 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
482 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
483 e->set_object ("slur", slur->self_scm ());
486 else if (avoid != ly_symbol2scm ("ignore"))
487 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
488 e->name ().c_str ()));
492 A callback that will be chained together with the original cross-staff
493 value of a grob that is placed 'outside or 'around a slur. This just says
494 that any grob becomes cross-staff if it is placed 'outside or 'around a
497 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
499 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
501 if (previous == SCM_BOOL_T)
504 Grob *me = unsmob_grob (smob);
505 Grob *slur = unsmob_grob (me->get_object ("slur"));
509 return slur->get_property ("cross-staff");
512 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
514 Slur::calc_cross_staff (SCM smob)
516 Grob *me = unsmob_grob (smob);
518 extract_grob_set (me, "note-columns", cols);
519 extract_grob_set (me, "encompass-objects", extras);
521 for (vsize i = 0; i < cols.size (); i++)
523 if (Grob *s = Note_column::get_stem (cols[i]))
524 if (to_boolean (s->get_property ("cross-staff")))
528 /* the separation items are dealt with in replace_breakable_encompass_objects
529 so we can ignore them here */
530 vector<Grob *> non_sep_extras;
531 for (vsize i = 0; i < extras.size (); i++)
532 if (!Separation_item::has_interface (extras[i]))
533 non_sep_extras.push_back (extras[i]);
535 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
536 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
538 return scm_from_bool (common != me->get_parent (Y_AXIS));
544 "The following properties may be set in the @code{details}"
548 "@item region-size\n"
549 "Size of region (in staff spaces) for determining"
550 " potential endpoints in the Y direction.\n"
551 "@item head-encompass-penalty\n"
552 "Demerit to apply when note heads collide with a slur.\n"
553 "@item stem-encompass-penalty\n"
554 "Demerit to apply when stems collide with a slur.\n"
555 "@item edge-attraction-factor\n"
556 "Factor used to calculate the demerit for distances"
557 " between slur endpoints and their corresponding base"
559 "@item same-slope-penalty\n"
560 "Demerit for slurs with attachment points that are"
561 " horizontally aligned.\n"
562 "@item steeper-slope-factor\n"
563 "Factor used to calculate demerit only if this slur is"
565 "@item non-horizontal-penalty\n"
566 "Demerit for slurs with attachment points that are not"
567 " horizontally aligned.\n"
569 "The maximum slope allowed for this slur.\n"
570 "@item max-slope-factor\n"
571 "Factor that calculates demerit based on the max slope.\n"
572 "@item free-head-distance\n"
573 "The amount of vertical free space that must exist"
574 " between a slur and note heads.\n"
575 "@item absolute-closeness-measure\n"
576 "Factor to calculate demerit for variance between a note"
578 "@item extra-object-collision-penalty\n"
579 "Factor to calculate demerit for extra objects that the"
580 " slur encompasses, including accidentals, fingerings, and"
582 "@item accidental-collision\n"
583 "Factor to calculate demerit for @code{Accidental} objects"
584 " that the slur encompasses. This property value replaces"
585 " the value of @code{extra-object-collision-penalty}.\n"
586 "@item extra-encompass-free-distance\n"
587 "The amount of vertical free space that must exist"
588 " between a slur and various objects it encompasses,"
589 " including accidentals, fingerings, and tuplet numbers.\n"
590 "@item extra-encompass-collision-distance\n"
591 "This detail is currently unused.\n"
592 "@item head-slur-distance-factor\n"
593 "Factor to calculate demerit for variance between a note"
595 "@item head-slur-distance-max-ratio\n"
596 "The maximum value for the ratio of distance between a"
597 " note head and slur.\n"
598 "@item free-slur-distance\n"
599 "The amount of vertical free space that must exist"
600 " between adjacent slurs. This subproperty only works"
601 " for @code{PhrasingSlur}.\n"
602 "@item edge-slope-exponent\n"
603 "Factor used to calculate the demerit for the slope of"
604 " a slur near its endpoints; a larger value yields a"
610 "avoid-slur " /* UGH. */