]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Fix beaming problems with cross-staff beams and slurs.
[lilypond.git] / lily / slur.cc
1 /*
2   slur.cc -- implement external interface for Slur
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8 */
9
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"
32
33
34
35 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
36 SCM
37 Slur::calc_direction (SCM smob)
38 {
39   Grob *me = unsmob_grob (smob);
40   extract_grob_set (me, "note-columns", encompasses);
41
42   if (encompasses.empty ())
43     {
44       me->suicide ();
45       return SCM_BOOL_F;
46     }
47
48   Direction d = DOWN;
49   for (vsize i = 0; i < encompasses.size (); i++)
50     {
51       if (Note_column::dir (encompasses[i]) < 0)
52         {
53           d = UP;
54           break;
55         }
56     }
57   return scm_from_int (d);
58 }
59
60 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
61 SCM
62 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
63 {
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);
68
69   extract_grob_set (me, "note-columns", encompasses);
70   Interval ret;
71
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 ());
77
78   for (vsize i = 0; i < encompasses.size (); i++)
79     {
80       Interval d = encompasses[i]->pure_height (parent, start, end);
81       if (!d.is_empty ())
82         ret.unite (d);
83     }
84
85   ret.widen (height * 0.5);
86   return ly_interval2scm (ret);
87 }
88
89 MAKE_SCHEME_CALLBACK (Slur, height, 1);
90 SCM
91 Slur::height (SCM smob)
92 {
93   Grob *me = unsmob_grob (smob);
94
95   // FIXME uncached
96   Stencil *m = me->get_stencil ();
97   return m ? ly_interval2scm (m->extent (Y_AXIS))
98     : ly_interval2scm (Interval ());
99 }
100
101 MAKE_SCHEME_CALLBACK (Slur, print, 1);
102 SCM
103 Slur::print (SCM smob)
104 {
105   Grob *me = unsmob_grob (smob);
106   extract_grob_set (me, "note-columns", encompasses);
107   if (encompasses.empty ())
108     {
109       me->suicide ();
110       return SCM_EOL;
111     }
112
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);
118
119   Bezier one = get_curve (me);
120   Stencil a;
121
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);
131
132 #if DEBUG_SLUR_SCORING
133   SCM annotation = me->get_property ("annotation");
134   if (!scm_is_string (annotation))
135     {
136       SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
137       if (to_boolean (debug))
138         annotation = me->get_property ("quant-score");
139     }
140   
141   if (scm_is_string (annotation))
142     {
143       string str;
144       SCM properties = Font_interface::text_font_alist_chain (me);
145
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);
149       
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);
154     }
155 #endif
156
157   return a.smobbed_copy ();
158 }
159
160
161 /*
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
165 */
166 void
167 Slur::replace_breakable_encompass_objects (Grob *me)
168 {
169   extract_grob_set (me, "encompass-objects", extra_objects);
170   vector<Grob *> new_encompasses;
171
172   for (vsize i = 0; i < extra_objects.size (); i++)
173     {
174       Grob *g = extra_objects[i];
175       
176       if (Separation_item::has_interface (g))
177         {
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]);
185         }
186       else
187         new_encompasses.push_back (g);
188     }
189
190   SCM encompass_scm = me->get_object ("encompass-objects");
191   if (Grob_array::unsmob (encompass_scm))
192     {
193       vector<Grob *> &arr =
194         unsmob_grob_array (encompass_scm)->array_reference ();
195       arr = new_encompasses;
196     }
197 }
198
199 Bezier
200 Slur::get_curve (Grob *me)
201 {
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));
207
208   return b;
209 }
210
211 void
212 Slur::add_column (Grob *me, Grob *n)
213 {
214   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
215   add_bound_item (dynamic_cast<Spanner *> (me), n);
216 }
217
218 void
219 Slur::add_extra_encompass (Grob *me, Grob *n)
220 {
221   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
222 }
223
224 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
225 SCM
226 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
227 {
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;
234
235   SCM avoid = script->get_property ("avoid-slur");
236   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
237     return offset_scm;
238
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);
242 }
243
244 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
245 SCM
246 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
247 {
248   Grob *script = unsmob_grob (grob);
249   Grob *slur = unsmob_grob (script->get_object ("slur")); 
250
251   if (!slur)
252     return offset_scm;
253
254   SCM avoid = script->get_property ("avoid-slur");
255   if (avoid != ly_symbol2scm ("outside")
256       && avoid != ly_symbol2scm ("around"))
257     return offset_scm;
258   
259   Direction dir = get_grob_direction (script);
260   if (dir == CENTER)
261     return offset_scm;
262
263   Grob *cx = script->common_refpoint (slur, X_AXIS);
264   Grob *cy = script->common_refpoint (slur, Y_AXIS);
265
266   Bezier curve = Slur::get_curve (slur);
267
268   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
269                            slur->relative_coordinate (cy, Y_AXIS)));
270
271   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
272   Interval xext = robust_relative_extent (script, cx, X_AXIS);
273
274   Real offset = robust_scm2double (offset_scm, 0);
275   yext.translate (offset);
276   
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);
281
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;
287   
288   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
289     {
290       Real x = xext.linear_combination ((Direction) d);
291       consider[k] = bezext.contains (x);
292
293       if (consider[k])
294         {
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));
301
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;
307         }
308     }
309
310   Real avoidance_offset = 0.0;
311   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
312     if (consider[k]) 
313       avoidance_offset = dir * (max (dir * avoidance_offset,
314                                      dir * (ys[k] - yext[-dir] + dir * slur_padding)));
315   
316   return scm_from_double (offset + avoidance_offset);
317 }
318
319 /*
320  * Used by Slur_engraver:: and Phrasing_slur_engraver::
321  */
322 void
323 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
324                                           vector<Grob*> &slurs,
325                                           vector<Grob*> &end_slurs)
326 {
327   if (slurs.empty () && end_slurs.empty ())
328     return;
329   
330   Grob *e = info.grob ();
331   SCM avoid = e->get_property ("avoid-slur");
332   if (Tie::has_interface (e)
333       || avoid == ly_symbol2scm ("inside"))
334     {
335       for (vsize i = slurs.size (); i--;)
336         add_extra_encompass (slurs[i], e);
337       for (vsize i = end_slurs.size (); i--;)
338         add_extra_encompass (end_slurs[i], e);
339     }
340   else if (avoid == ly_symbol2scm ("outside")
341            || avoid == ly_symbol2scm ("around"))
342     {
343       Grob *slur;
344       if (end_slurs.size () && !slurs.size ())
345         slur = end_slurs[0];
346       else
347         slur = slurs[0];
348
349       if (slur)
350         {
351           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
352           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
353           e->set_object ("slur", slur->self_scm ());
354         }
355     }
356   else
357     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
358                     e->name().c_str ()));
359 }
360
361 /*
362   A callback that will be chained together with the original cross-staff
363   value of a grob that is placed 'outside or 'around a slur. This just says
364   that any grob becomes cross-staff if it is placed 'outside or 'around a
365   cross-staff slur.
366 */
367 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
368 SCM
369 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
370 {
371   if (previous == SCM_BOOL_T)
372     return previous;
373
374   Grob *me = unsmob_grob (smob);
375   Grob *slur = unsmob_grob (me->get_object ("slur"));
376
377   if (!slur)
378     return SCM_BOOL_F;
379   return slur->get_property ("cross-staff");
380 }
381
382 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
383 SCM
384 Slur::calc_cross_staff (SCM smob)
385 {
386   Grob *me = unsmob_grob (smob);
387
388   extract_grob_set (me, "note-columns", cols);
389   extract_grob_set (me, "encompass-objects", extras);
390
391   for (vsize i = 0; i < cols.size (); i++)
392     {
393       if (Grob *s = Note_column::get_stem (cols[i]))
394         if (to_boolean (s->get_property ("cross-staff")))
395           return SCM_BOOL_T;
396     }
397
398   /* the separation items are dealt with in replace_breakable_encompass_objects
399      so we can ignore them here */
400   vector<Grob*> non_sep_extras;
401   for (vsize i = 0; i < extras.size (); i++)
402     if (!Separation_item::has_interface (extras[i]))
403       non_sep_extras.push_back (extras[i]);
404
405   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
406   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
407
408   return scm_from_bool (common != me->get_parent (Y_AXIS));
409 }
410
411 ADD_INTERFACE (Slur,
412                "A slur.",
413                
414                /* properties */
415                "annotation "
416                "avoid-slur "    /* UGH. */
417                "control-points "
418                "dash-fraction "
419                "dash-period "
420                "details "
421                "direction "
422                "eccentricity "
423                "encompass-objects "
424                "height-limit "
425                "inspect-quants "
426                "inspect-index "
427                "line-thickness "
428                "note-columns "
429                "positions "
430                "quant-score "
431                "ratio "
432                "thickness "
433                );
434