Update from Andrew: tweaks for cross-staff chords snippet.
[lilypond.git] / lily / slur.cc
blob604221e3f2d2087ce86186b17ea074172e5cedc6
1 /*
2 slur.cc -- implement external interface for Slur
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2007 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 p = me->get_property ("dash-period");
123 SCM f = me->get_property ("dash-fraction");
124 if (scm_is_number (p) && scm_is_number (f))
125 a = Lookup::dashed_slur (one, line_thick, robust_scm2double (p, 1.0),
126 robust_scm2double (f, 0));
127 else
128 a = Lookup::slur (one,
129 get_grob_direction (me) * base_thick,
130 line_thick);
132 #if DEBUG_SLUR_SCORING
133 SCM annotation = me->get_property ("annotation");
134 if (!scm_is_string (annotation))
136 SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
137 if (to_boolean (debug))
138 annotation = me->get_property ("quant-score");
141 if (scm_is_string (annotation))
143 string str;
144 SCM properties = Font_interface::text_font_alist_chain (me);
146 if (!scm_is_number (me->get_property ("font-size")))
147 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
148 properties);
150 Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
151 (me->layout ()->self_scm (), properties,
152 annotation));
153 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
155 #endif
157 return a.smobbed_copy ();
162 it would be better to do this at engraver level, but that is
163 fragile, as the breakabl items are generated on staff level, at
164 which point slur starts and ends have to be tracked
166 void
167 Slur::replace_breakable_encompass_objects (Grob *me)
169 extract_grob_set (me, "encompass-objects", extra_objects);
170 vector<Grob *> new_encompasses;
172 for (vsize i = 0; i < extra_objects.size (); i++)
174 Grob *g = extra_objects[i];
176 if (Separation_item::has_interface (g))
178 extract_grob_set (g, "elements", breakables);
179 for (vsize j = 0; j < breakables.size (); j++)
180 /* if we encompass a separation-item that spans multiple staves,
181 we filter out the grobs that don't belong to our staff */
182 if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
183 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
184 new_encompasses.push_back (breakables[j]);
186 else
187 new_encompasses.push_back (g);
190 SCM encompass_scm = me->get_object ("encompass-objects");
191 if (Grob_array::unsmob (encompass_scm))
193 vector<Grob *> &arr =
194 unsmob_grob_array (encompass_scm)->array_reference ();
195 arr = new_encompasses;
199 Bezier
200 Slur::get_curve (Grob *me)
202 Bezier b;
203 int i = 0;
204 for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
205 s = scm_cdr (s))
206 b.control_[i++] = ly_scm2offset (scm_car (s));
208 return b;
211 void
212 Slur::add_column (Grob *me, Grob *n)
214 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
215 add_bound_item (dynamic_cast<Spanner *> (me), n);
218 void
219 Slur::add_extra_encompass (Grob *me, Grob *n)
221 Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
224 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
226 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
228 int start = robust_scm2int (start_scm, 0);
229 int end = robust_scm2int (end_scm, 0);
230 Grob *script = unsmob_grob (grob);
231 Grob *slur = unsmob_grob (script->get_object ("slur"));
232 if (!slur)
233 return offset_scm;
235 SCM avoid = script->get_property ("avoid-slur");
236 if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
237 return offset_scm;
239 Real offset = robust_scm2double (offset_scm, 0.0);
240 Direction dir = get_grob_direction (script);
241 return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
244 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
246 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
248 Grob *script = unsmob_grob (grob);
249 Grob *slur = unsmob_grob (script->get_object ("slur"));
251 if (!slur)
252 return offset_scm;
254 SCM avoid = script->get_property ("avoid-slur");
255 if (avoid != ly_symbol2scm ("outside")
256 && avoid != ly_symbol2scm ("around"))
257 return offset_scm;
259 Direction dir = get_grob_direction (script);
260 if (dir == CENTER)
261 return offset_scm;
263 Grob *cx = script->common_refpoint (slur, X_AXIS);
264 Grob *cy = script->common_refpoint (slur, Y_AXIS);
266 Bezier curve = Slur::get_curve (slur);
268 curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
269 slur->relative_coordinate (cy, Y_AXIS)));
271 Interval yext = robust_relative_extent (script, cy, Y_AXIS);
272 Interval xext = robust_relative_extent (script, cx, X_AXIS);
274 Real offset = robust_scm2double (offset_scm, 0);
275 yext.translate (offset);
277 /* FIXME: slur property, script property? */
278 Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
279 0.0);
280 yext.widen (slur_padding);
282 Real EPS = 1e-3;
283 Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
284 bool consider[] = { false, false, false };
285 Real ys[] = {0, 0, 0};
286 bool do_shift = false;
288 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
290 Real x = xext.linear_combination ((Direction) d);
291 consider[k] = bezext.contains (x);
293 if (consider[k])
295 ys[k]
296 = (fabs (bezext[LEFT] - x) < EPS)
297 ? curve.control_[0][Y_AXIS]
298 : ((fabs (bezext[RIGHT] - x) < EPS)
299 ? curve.control_[3][Y_AXIS]
300 : curve.get_other_coordinate (X_AXIS, x));
302 /* Request shift if slur is contained script's Y, or if
303 script is inside slur and avoid == outside. */
304 if (yext.contains (ys[k])
305 || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
306 do_shift = true;
310 Real avoidance_offset = 0.0;
311 if (do_shift)
313 for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
314 if (consider[k])
315 avoidance_offset = dir * (max (dir * avoidance_offset,
316 dir * (ys[k] - yext[-dir] + dir * slur_padding)));
318 return scm_from_double (offset + avoidance_offset);
322 * Used by Slur_engraver:: and Phrasing_slur_engraver::
324 void
325 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
326 vector<Grob*> &slurs,
327 vector<Grob*> &end_slurs)
329 if (slurs.empty () && end_slurs.empty ())
330 return;
332 Grob *e = info.grob ();
333 SCM avoid = e->get_property ("avoid-slur");
334 if (Tie::has_interface (e)
335 || avoid == ly_symbol2scm ("inside"))
337 for (vsize i = slurs.size (); i--;)
338 add_extra_encompass (slurs[i], e);
339 for (vsize i = end_slurs.size (); i--;)
340 add_extra_encompass (end_slurs[i], e);
342 else if (avoid == ly_symbol2scm ("outside")
343 || avoid == ly_symbol2scm ("around"))
345 Grob *slur;
346 if (end_slurs.size () && !slurs.size ())
347 slur = end_slurs[0];
348 else
349 slur = slurs[0];
351 if (slur)
353 chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
354 chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
355 e->set_object ("slur", slur->self_scm ());
358 else
359 e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
360 e->name().c_str ()));
364 A callback that will be chained together with the original cross-staff
365 value of a grob that is placed 'outside or 'around a slur. This just says
366 that any grob becomes cross-staff if it is placed 'outside or 'around a
367 cross-staff slur.
369 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
371 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
373 if (previous == SCM_BOOL_T)
374 return previous;
376 Grob *me = unsmob_grob (smob);
377 Grob *slur = unsmob_grob (me->get_object ("slur"));
379 if (!slur)
380 return SCM_BOOL_F;
381 return slur->get_property ("cross-staff");
384 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
386 Slur::calc_cross_staff (SCM smob)
388 Grob *me = unsmob_grob (smob);
390 extract_grob_set (me, "note-columns", cols);
391 extract_grob_set (me, "encompass-objects", extras);
393 for (vsize i = 0; i < cols.size (); i++)
395 if (Grob *s = Note_column::get_stem (cols[i]))
396 if (to_boolean (s->get_property ("cross-staff")))
397 return SCM_BOOL_T;
400 /* the separation items are dealt with in replace_breakable_encompass_objects
401 so we can ignore them here */
402 vector<Grob*> non_sep_extras;
403 for (vsize i = 0; i < extras.size (); i++)
404 if (!Separation_item::has_interface (extras[i]))
405 non_sep_extras.push_back (extras[i]);
407 Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
408 common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
410 return scm_from_bool (common != me->get_parent (Y_AXIS));
413 ADD_INTERFACE (Slur,
414 "A slur.",
416 /* properties */
417 "annotation "
418 "avoid-slur " /* UGH. */
419 "control-points "
420 "dash-fraction "
421 "dash-period "
422 "details "
423 "direction "
424 "eccentricity "
425 "encompass-objects "
426 "height-limit "
427 "inspect-quants "
428 "inspect-index "
429 "line-thickness "
430 "note-columns "
431 "positions "
432 "quant-score "
433 "ratio "
434 "thickness "