Ensure scripts inside chords obey manual directions.
[lilypond/mpolesky.git] / lily / slur.cc
blob1b5b05ef3ac3e02ebff303cde6b90692ad462a98
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2010 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 SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
147 if (to_boolean (debug))
148 annotation = me->get_property ("quant-score");
151 if (scm_is_string (annotation))
153 string str;
154 SCM properties = Font_interface::text_font_alist_chain (me);
156 if (!scm_is_number (me->get_property ("font-size")))
157 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
158 properties);
160 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
161 (me->layout ()->self_scm (), properties,
162 annotation));
163 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
165 #endif
167 return a.smobbed_copy ();
172 it would be better to do this at engraver level, but that is
173 fragile, as the breakable items are generated on staff level, at
174 which point slur starts and ends have to be tracked
176 void
177 Slur::replace_breakable_encompass_objects (Grob *me)
179 extract_grob_set (me, "encompass-objects", extra_objects);
180 vector<Grob *> new_encompasses;
182 for (vsize i = 0; i < extra_objects.size (); i++)
184 Grob *g = extra_objects[i];
186 if (Separation_item::has_interface (g))
188 extract_grob_set (g, "elements", breakables);
189 for (vsize j = 0; j < breakables.size (); j++)
190 /* if we encompass a separation-item that spans multiple staves,
191 we filter out the grobs that don't belong to our staff */
192 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
193 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
194 new_encompasses.push_back (breakables[j]);
196 else
197 new_encompasses.push_back (g);
200 SCM encompass_scm = me->get_object ("encompass-objects");
201 if (Grob_array::unsmob (encompass_scm))
203 vector<Grob *> &arr =
204 unsmob_grob_array (encompass_scm)->array_reference ();
205 arr = new_encompasses;
209 Bezier
210 Slur::get_curve (Grob *me)
212 Bezier b;
213 int i = 0;
214 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
215 s = scm_cdr (s))
216 b.control_[i++] = ly_scm2offset (scm_car (s));
218 return b;
221 void
222 Slur::add_column (Grob *me, Grob *n)
224 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
225 add_bound_item (dynamic_cast<Spanner *> (me), n);
228 void
229 Slur::add_extra_encompass (Grob *me, Grob *n)
231 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
234 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
236 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
238 int start = robust_scm2int (start_scm, 0);
239 int end = robust_scm2int (end_scm, 0);
240 Grob *script = unsmob_grob (grob);
241 Grob *slur = unsmob_grob (script->get_object ("slur"));
242 if (!slur)
243 return offset_scm;
245 SCM avoid = script->get_property ("avoid-slur");
246 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
247 return offset_scm;
249 Real offset = robust_scm2double (offset_scm, 0.0);
250 Direction dir = get_grob_direction (script);
251 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
254 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
256 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
258 Grob *script = unsmob_grob (grob);
259 Grob *slur = unsmob_grob (script->get_object ("slur"));
261 if (!slur)
262 return offset_scm;
264 SCM avoid = script->get_property ("avoid-slur");
265 if (avoid != ly_symbol2scm ("outside")
266 && avoid != ly_symbol2scm ("around"))
267 return offset_scm;
269 Direction dir = get_grob_direction (script);
270 if (dir == CENTER)
271 return offset_scm;
273 Grob *cx = script->common_refpoint (slur, X_AXIS);
274 Grob *cy = script->common_refpoint (slur, Y_AXIS);
276 Bezier curve = Slur::get_curve (slur);
278 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
279 slur->relative_coordinate (cy, Y_AXIS)));
281 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
282 Interval xext = robust_relative_extent (script, cx, X_AXIS);
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"),
289 0.0);
290 yext.widen (slur_padding);
292 const Real EPS = 1e-3;
293 Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
294 bool consider[] = {false, false, false};
295 Real ys[] = {0, 0, 0};
296 bool do_shift = false;
298 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
300 Real x = xext.linear_combination ((Direction) d);
301 consider[k] = bezext.contains (x);
303 if (consider[k])
305 ys[k]
306 = (fabs (bezext[LEFT] - x) < EPS)
307 ? curve.control_[0][Y_AXIS]
308 : ((fabs (bezext[RIGHT] - x) < EPS)
309 ? curve.control_[3][Y_AXIS]
310 : curve.get_other_coordinate (X_AXIS, x));
312 /* Request shift if slur is contained script's Y, or if
313 script is inside slur and avoid == outside. */
314 if (yext.contains (ys[k])
315 || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
316 do_shift = true;
320 Real avoidance_offset = 0.0;
321 if (do_shift)
323 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
324 if (consider[k])
325 avoidance_offset = dir * (max (dir * avoidance_offset,
326 dir * (ys[k] - yext[-dir] + dir * slur_padding)));
328 return scm_from_double (offset + avoidance_offset);
332 * Used by Slur_engraver:: and Phrasing_slur_engraver::
334 void
335 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
336 vector<Grob*> &slurs,
337 vector<Grob*> &end_slurs)
339 if (slurs.empty () && end_slurs.empty ())
340 return;
342 Grob *e = info.grob ();
343 SCM avoid = e->get_property ("avoid-slur");
344 if (Tie::has_interface (e)
345 || avoid == ly_symbol2scm ("inside"))
347 for (vsize i = slurs.size (); i--;)
348 add_extra_encompass (slurs[i], e);
349 for (vsize i = end_slurs.size (); i--;)
350 add_extra_encompass (end_slurs[i], e);
352 else if (avoid == ly_symbol2scm ("outside")
353 || avoid == ly_symbol2scm ("around"))
355 Grob *slur;
356 if (end_slurs.size () && !slurs.size ())
357 slur = end_slurs[0];
358 else
359 slur = slurs[0];
361 if (slur)
363 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
364 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
365 e->set_object ("slur", slur->self_scm ());
368 else if (avoid != ly_symbol2scm ("ignore"))
369 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
370 e->name().c_str ()));
374 A callback that will be chained together with the original cross-staff
375 value of a grob that is placed 'outside or 'around a slur. This just says
376 that any grob becomes cross-staff if it is placed 'outside or 'around a
377 cross-staff slur.
379 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
381 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
383 if (previous == SCM_BOOL_T)
384 return previous;
386 Grob *me = unsmob_grob (smob);
387 Grob *slur = unsmob_grob (me->get_object ("slur"));
389 if (!slur)
390 return SCM_BOOL_F;
391 return slur->get_property ("cross-staff");
394 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
396 Slur::calc_cross_staff (SCM smob)
398 Grob *me = unsmob_grob (smob);
400 extract_grob_set (me, "note-columns", cols);
401 extract_grob_set (me, "encompass-objects", extras);
403 for (vsize i = 0; i < cols.size (); i++)
405 if (Grob *s = Note_column::get_stem (cols[i]))
406 if (to_boolean (s->get_property ("cross-staff")))
407 return SCM_BOOL_T;
410 /* the separation items are dealt with in replace_breakable_encompass_objects
411 so we can ignore them here */
412 vector<Grob*> non_sep_extras;
413 for (vsize i = 0; i < extras.size (); i++)
414 if (!Separation_item::has_interface (extras[i]))
415 non_sep_extras.push_back (extras[i]);
417 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
418 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
420 return scm_from_bool (common != me->get_parent (Y_AXIS));
423 ADD_INTERFACE (Slur,
424 "A slur.",
426 /* properties */
427 "annotation "
428 "avoid-slur " /* UGH. */
429 "control-points "
430 "dash-definition "
431 "details "
432 "direction "
433 "eccentricity "
434 "encompass-objects "
435 "height-limit "
436 "inspect-quants "
437 "inspect-index "
438 "line-thickness "
439 "note-columns "
440 "positions "
441 "quant-score "
442 "ratio "
443 "thickness "