Nitpick: ly:spanner-bound grob name slur -> spanner.
[lilypond.git] / lily / slur.cc
blob7eea1c24d08597dce719e2578ac3a5893ea73470
1 /*
2 slur.cc -- implement external interface for Slur
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
8 */
10 #include "slur.hh"
11 #include "grob-info.hh"
12 #include "grob-array.hh"
13 #include "beam.hh"
14 #include "bezier.hh"
15 #include "directional-element-interface.hh"
16 #include "font-interface.hh"
17 #include "item.hh"
18 #include "pointer-group-interface.hh"
19 #include "lookup.hh"
20 #include "main.hh" // DEBUG_SLUR_SCORING
21 #include "note-column.hh"
22 #include "output-def.hh"
23 #include "spanner.hh"
24 #include "staff-symbol-referencer.hh"
25 #include "stem.hh"
26 #include "text-interface.hh"
27 #include "tie.hh"
28 #include "warn.hh"
29 #include "slur-scoring.hh"
30 #include "separation-item.hh"
31 #include "international.hh"
35 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
36 SCM
37 Slur::calc_direction (SCM smob)
39 Grob *me = unsmob_grob (smob);
40 extract_grob_set (me, "note-columns", encompasses);
42 if (encompasses.empty ())
44 me->suicide ();
45 return SCM_BOOL_F;
48 Direction d = DOWN;
49 for (vsize i = 0; i < encompasses.size (); i++)
51 if (Note_column::dir (encompasses[i]) < 0)
53 d = UP;
54 break;
57 return scm_from_int (d);
60 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
61 SCM
62 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
64 Grob *me = unsmob_grob (smob);
65 int start = scm_to_int (start_scm);
66 int end = scm_to_int (end_scm);
67 Real height = robust_scm2double (me->get_property ("height-limit"), 2.0);
69 extract_grob_set (me, "note-columns", encompasses);
70 Interval ret;
72 Grob *parent = me->get_parent (Y_AXIS);
73 if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
74 /* this could happen if, for example, we are a cross-staff slur.
75 in this case, we want to be ignored */
76 return ly_interval2scm (Interval ());
78 for (vsize i = 0; i < encompasses.size (); i++)
80 Interval d = encompasses[i]->pure_height (parent, start, end);
81 if (!d.is_empty ())
82 ret.unite (d);
85 ret.widen (height * 0.5);
86 return ly_interval2scm (ret);
89 MAKE_SCHEME_CALLBACK (Slur, height, 1);
90 SCM
91 Slur::height (SCM smob)
93 Grob *me = unsmob_grob (smob);
95 // FIXME uncached
96 Stencil *m = me->get_stencil ();
97 return m ? ly_interval2scm (m->extent (Y_AXIS))
98 : ly_interval2scm (Interval ());
101 MAKE_SCHEME_CALLBACK (Slur, print, 1);
103 Slur::print (SCM smob)
105 Grob *me = unsmob_grob (smob);
106 extract_grob_set (me, "note-columns", encompasses);
107 if (encompasses.empty ())
109 me->suicide ();
110 return SCM_EOL;
113 Real staff_thick = Staff_symbol_referencer::line_thickness (me);
114 Real base_thick = staff_thick
115 * robust_scm2double (me->get_property ("thickness"), 1);
116 Real line_thick = staff_thick
117 * robust_scm2double (me->get_property ("line-thickness"), 1);
119 Bezier one = get_curve (me);
120 Stencil a;
122 SCM dash_definition = me->get_property ("dash-definition");
123 a = Lookup::slur (one,
124 get_grob_direction (me) * base_thick,
125 line_thick,
126 dash_definition);
128 #if DEBUG_SLUR_SCORING
129 SCM annotation = me->get_property ("annotation");
130 if (!scm_is_string (annotation))
132 SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
133 if (to_boolean (debug))
134 annotation = me->get_property ("quant-score");
137 if (scm_is_string (annotation))
139 string str;
140 SCM properties = Font_interface::text_font_alist_chain (me);
142 if (!scm_is_number (me->get_property ("font-size")))
143 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
144 properties);
146 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
147 (me->layout ()->self_scm (), properties,
148 annotation));
149 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
151 #endif
153 return a.smobbed_copy ();
158 it would be better to do this at engraver level, but that is
159 fragile, as the breakabl items are generated on staff level, at
160 which point slur starts and ends have to be tracked
162 void
163 Slur::replace_breakable_encompass_objects (Grob *me)
165 extract_grob_set (me, "encompass-objects", extra_objects);
166 vector<Grob *> new_encompasses;
168 for (vsize i = 0; i < extra_objects.size (); i++)
170 Grob *g = extra_objects[i];
172 if (Separation_item::has_interface (g))
174 extract_grob_set (g, "elements", breakables);
175 for (vsize j = 0; j < breakables.size (); j++)
176 /* if we encompass a separation-item that spans multiple staves,
177 we filter out the grobs that don't belong to our staff */
178 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
179 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
180 new_encompasses.push_back (breakables[j]);
182 else
183 new_encompasses.push_back (g);
186 SCM encompass_scm = me->get_object ("encompass-objects");
187 if (Grob_array::unsmob (encompass_scm))
189 vector<Grob *> &arr =
190 unsmob_grob_array (encompass_scm)->array_reference ();
191 arr = new_encompasses;
195 Bezier
196 Slur::get_curve (Grob *me)
198 Bezier b;
199 int i = 0;
200 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
201 s = scm_cdr (s))
202 b.control_[i++] = ly_scm2offset (scm_car (s));
204 return b;
207 void
208 Slur::add_column (Grob *me, Grob *n)
210 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
211 add_bound_item (dynamic_cast<Spanner *> (me), n);
214 void
215 Slur::add_extra_encompass (Grob *me, Grob *n)
217 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
220 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
222 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
224 int start = robust_scm2int (start_scm, 0);
225 int end = robust_scm2int (end_scm, 0);
226 Grob *script = unsmob_grob (grob);
227 Grob *slur = unsmob_grob (script->get_object ("slur"));
228 if (!slur)
229 return offset_scm;
231 SCM avoid = script->get_property ("avoid-slur");
232 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
233 return offset_scm;
235 Real offset = robust_scm2double (offset_scm, 0.0);
236 Direction dir = get_grob_direction (script);
237 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
240 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
242 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
244 Grob *script = unsmob_grob (grob);
245 Grob *slur = unsmob_grob (script->get_object ("slur"));
247 if (!slur)
248 return offset_scm;
250 SCM avoid = script->get_property ("avoid-slur");
251 if (avoid != ly_symbol2scm ("outside")
252 && avoid != ly_symbol2scm ("around"))
253 return offset_scm;
255 Direction dir = get_grob_direction (script);
256 if (dir == CENTER)
257 return offset_scm;
259 Grob *cx = script->common_refpoint (slur, X_AXIS);
260 Grob *cy = script->common_refpoint (slur, Y_AXIS);
262 Bezier curve = Slur::get_curve (slur);
264 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
265 slur->relative_coordinate (cy, Y_AXIS)));
267 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
268 Interval xext = robust_relative_extent (script, cx, X_AXIS);
270 Real offset = robust_scm2double (offset_scm, 0);
271 yext.translate (offset);
273 /* FIXME: slur property, script property? */
274 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
275 0.0);
276 yext.widen (slur_padding);
278 Real EPS = 1e-3;
279 Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
280 bool consider[] = { false, false, false };
281 Real ys[] = {0, 0, 0};
282 bool do_shift = false;
284 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
286 Real x = xext.linear_combination ((Direction) d);
287 consider[k] = bezext.contains (x);
289 if (consider[k])
291 ys[k]
292 = (fabs (bezext[LEFT] - x) < EPS)
293 ? curve.control_[0][Y_AXIS]
294 : ((fabs (bezext[RIGHT] - x) < EPS)
295 ? curve.control_[3][Y_AXIS]
296 : curve.get_other_coordinate (X_AXIS, x));
298 /* Request shift if slur is contained script's Y, or if
299 script is inside slur and avoid == outside. */
300 if (yext.contains (ys[k])
301 || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
302 do_shift = true;
306 Real avoidance_offset = 0.0;
307 if (do_shift)
309 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
310 if (consider[k])
311 avoidance_offset = dir * (max (dir * avoidance_offset,
312 dir * (ys[k] - yext[-dir] + dir * slur_padding)));
314 return scm_from_double (offset + avoidance_offset);
318 * Used by Slur_engraver:: and Phrasing_slur_engraver::
320 void
321 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
322 vector<Grob*> &slurs,
323 vector<Grob*> &end_slurs)
325 if (slurs.empty () && end_slurs.empty ())
326 return;
328 Grob *e = info.grob ();
329 SCM avoid = e->get_property ("avoid-slur");
330 if (Tie::has_interface (e)
331 || avoid == ly_symbol2scm ("inside"))
333 for (vsize i = slurs.size (); i--;)
334 add_extra_encompass (slurs[i], e);
335 for (vsize i = end_slurs.size (); i--;)
336 add_extra_encompass (end_slurs[i], e);
338 else if (avoid == ly_symbol2scm ("outside")
339 || avoid == ly_symbol2scm ("around"))
341 Grob *slur;
342 if (end_slurs.size () && !slurs.size ())
343 slur = end_slurs[0];
344 else
345 slur = slurs[0];
347 if (slur)
349 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
350 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
351 e->set_object ("slur", slur->self_scm ());
354 else
355 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
356 e->name().c_str ()));
360 A callback that will be chained together with the original cross-staff
361 value of a grob that is placed 'outside or 'around a slur. This just says
362 that any grob becomes cross-staff if it is placed 'outside or 'around a
363 cross-staff slur.
365 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
367 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
369 if (previous == SCM_BOOL_T)
370 return previous;
372 Grob *me = unsmob_grob (smob);
373 Grob *slur = unsmob_grob (me->get_object ("slur"));
375 if (!slur)
376 return SCM_BOOL_F;
377 return slur->get_property ("cross-staff");
380 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
382 Slur::calc_cross_staff (SCM smob)
384 Grob *me = unsmob_grob (smob);
386 extract_grob_set (me, "note-columns", cols);
387 extract_grob_set (me, "encompass-objects", extras);
389 for (vsize i = 0; i < cols.size (); i++)
391 if (Grob *s = Note_column::get_stem (cols[i]))
392 if (to_boolean (s->get_property ("cross-staff")))
393 return SCM_BOOL_T;
396 /* the separation items are dealt with in replace_breakable_encompass_objects
397 so we can ignore them here */
398 vector<Grob*> non_sep_extras;
399 for (vsize i = 0; i < extras.size (); i++)
400 if (!Separation_item::has_interface (extras[i]))
401 non_sep_extras.push_back (extras[i]);
403 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
404 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
406 return scm_from_bool (common != me->get_parent (Y_AXIS));
409 ADD_INTERFACE (Slur,
410 "A slur.",
412 /* properties */
413 "annotation "
414 "avoid-slur " /* UGH. */
415 "control-points "
416 "dash-definition "
417 "details "
418 "direction "
419 "eccentricity "
420 "encompass-objects "
421 "height-limit "
422 "inspect-quants "
423 "inspect-index "
424 "line-thickness "
425 "note-columns "
426 "positions "
427 "quant-score "
428 "ratio "
429 "thickness "