]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Merge branch 'master' of ssh://jomand@git.sv.gnu.org/srv/git/lilypond
[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--2009 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 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);
127
128 #if DEBUG_SLUR_SCORING
129   SCM annotation = me->get_property ("annotation");
130   if (!scm_is_string (annotation))
131     {
132       SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
133       if (to_boolean (debug))
134         annotation = me->get_property ("quant-score");
135     }
136   
137   if (scm_is_string (annotation))
138     {
139       string str;
140       SCM properties = Font_interface::text_font_alist_chain (me);
141
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);
145       
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);
150     }
151 #endif
152
153   return a.smobbed_copy ();
154 }
155
156
157 /*
158   it would be better to do this at engraver level, but that is
159   fragile, as the breakable items are generated on staff level, at
160   which point slur starts and ends have to be tracked
161 */
162 void
163 Slur::replace_breakable_encompass_objects (Grob *me)
164 {
165   extract_grob_set (me, "encompass-objects", extra_objects);
166   vector<Grob *> new_encompasses;
167
168   for (vsize i = 0; i < extra_objects.size (); i++)
169     {
170       Grob *g = extra_objects[i];
171       
172       if (Separation_item::has_interface (g))
173         {
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]);
181         }
182       else
183         new_encompasses.push_back (g);
184     }
185
186   SCM encompass_scm = me->get_object ("encompass-objects");
187   if (Grob_array::unsmob (encompass_scm))
188     {
189       vector<Grob *> &arr =
190         unsmob_grob_array (encompass_scm)->array_reference ();
191       arr = new_encompasses;
192     }
193 }
194
195 Bezier
196 Slur::get_curve (Grob *me)
197 {
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));
203
204   return b;
205 }
206
207 void
208 Slur::add_column (Grob *me, Grob *n)
209 {
210   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
211   add_bound_item (dynamic_cast<Spanner *> (me), n);
212 }
213
214 void
215 Slur::add_extra_encompass (Grob *me, Grob *n)
216 {
217   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
218 }
219
220 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
221 SCM
222 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
223 {
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;
230
231   SCM avoid = script->get_property ("avoid-slur");
232   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
233     return offset_scm;
234
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);
238 }
239
240 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
241 SCM
242 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
243 {
244   Grob *script = unsmob_grob (grob);
245   Grob *slur = unsmob_grob (script->get_object ("slur")); 
246
247   if (!slur)
248     return offset_scm;
249
250   SCM avoid = script->get_property ("avoid-slur");
251   if (avoid != ly_symbol2scm ("outside")
252       && avoid != ly_symbol2scm ("around"))
253     return offset_scm;
254   
255   Direction dir = get_grob_direction (script);
256   if (dir == CENTER)
257     return offset_scm;
258
259   Grob *cx = script->common_refpoint (slur, X_AXIS);
260   Grob *cy = script->common_refpoint (slur, Y_AXIS);
261
262   Bezier curve = Slur::get_curve (slur);
263
264   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
265                            slur->relative_coordinate (cy, Y_AXIS)));
266
267   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
268   Interval xext = robust_relative_extent (script, cx, X_AXIS);
269
270   Real offset = robust_scm2double (offset_scm, 0);
271   yext.translate (offset);
272   
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);
277
278   const 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;
283   
284   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
285     {
286       Real x = xext.linear_combination ((Direction) d);
287       consider[k] = bezext.contains (x);
288
289       if (consider[k])
290         {
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));
297
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;
303         }
304     }
305
306   Real avoidance_offset = 0.0;
307   if (do_shift)
308     {
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)));
313     }
314   return scm_from_double (offset + avoidance_offset);
315 }
316
317 /*
318  * Used by Slur_engraver:: and Phrasing_slur_engraver::
319  */
320 void
321 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
322                                           vector<Grob*> &slurs,
323                                           vector<Grob*> &end_slurs)
324 {
325   if (slurs.empty () && end_slurs.empty ())
326     return;
327   
328   Grob *e = info.grob ();
329   SCM avoid = e->get_property ("avoid-slur");
330   if (Tie::has_interface (e)
331       || avoid == ly_symbol2scm ("inside"))
332     {
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);
337     }
338   else if (avoid == ly_symbol2scm ("outside")
339            || avoid == ly_symbol2scm ("around"))
340     {
341       Grob *slur;
342       if (end_slurs.size () && !slurs.size ())
343         slur = end_slurs[0];
344       else
345         slur = slurs[0];
346
347       if (slur)
348         {
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 ());
352         }
353     }
354   else if (avoid != ly_symbol2scm ("ignore"))
355     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
356                     e->name().c_str ()));
357 }
358
359 /*
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.
364 */
365 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
366 SCM
367 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
368 {
369   if (previous == SCM_BOOL_T)
370     return previous;
371
372   Grob *me = unsmob_grob (smob);
373   Grob *slur = unsmob_grob (me->get_object ("slur"));
374
375   if (!slur)
376     return SCM_BOOL_F;
377   return slur->get_property ("cross-staff");
378 }
379
380 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
381 SCM
382 Slur::calc_cross_staff (SCM smob)
383 {
384   Grob *me = unsmob_grob (smob);
385
386   extract_grob_set (me, "note-columns", cols);
387   extract_grob_set (me, "encompass-objects", extras);
388
389   for (vsize i = 0; i < cols.size (); i++)
390     {
391       if (Grob *s = Note_column::get_stem (cols[i]))
392         if (to_boolean (s->get_property ("cross-staff")))
393           return SCM_BOOL_T;
394     }
395
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]);
402
403   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
404   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
405
406   return scm_from_bool (common != me->get_parent (Y_AXIS));
407 }
408
409 ADD_INTERFACE (Slur,
410                "A slur.",
411                
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 "
430                );
431