]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Merge branch 'master' into lilypond/translation
[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 //  SCM p = me->get_property ("dash-period");
124 //  SCM f = me->get_property ("dash-fraction");
125 //  SCM interval = me->get_property ("dash-interval");
126 //  if (scm_is_number (p) && scm_is_number (f))
127 //    a = Lookup::dashed_slur (one, line_thick, robust_scm2double (p, 1.0),
128 //                           robust_scm2double (f, 0));
129 //  else
130     a = Lookup::slur (one,
131                       get_grob_direction (me) * base_thick,
132                       line_thick,
133                       dash_definition);
134 //                      robust_scm2double (p, 1.0),
135 //                      robust_scm2double (f, 0),
136 //                      robust_scm2double (interval, 1));
137
138 #if DEBUG_SLUR_SCORING
139   SCM annotation = me->get_property ("annotation");
140   if (!scm_is_string (annotation))
141     {
142       SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
143       if (to_boolean (debug))
144         annotation = me->get_property ("quant-score");
145     }
146   
147   if (scm_is_string (annotation))
148     {
149       string str;
150       SCM properties = Font_interface::text_font_alist_chain (me);
151
152       if (!scm_is_number (me->get_property ("font-size")))
153         properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
154                              properties);
155       
156       Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
157                                     (me->layout ()->self_scm (), properties,
158                                      annotation));
159       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
160     }
161 #endif
162
163   return a.smobbed_copy ();
164 }
165
166
167 /*
168   it would be better to do this at engraver level, but that is
169   fragile, as the breakabl items are generated on staff level, at
170   which point slur starts and ends have to be tracked
171 */
172 void
173 Slur::replace_breakable_encompass_objects (Grob *me)
174 {
175   extract_grob_set (me, "encompass-objects", extra_objects);
176   vector<Grob *> new_encompasses;
177
178   for (vsize i = 0; i < extra_objects.size (); i++)
179     {
180       Grob *g = extra_objects[i];
181       
182       if (Separation_item::has_interface (g))
183         {
184           extract_grob_set (g, "elements", breakables);
185           for (vsize j = 0; j < breakables.size (); j++)
186             /* if we encompass a separation-item that spans multiple staves,
187                we filter out the grobs that don't belong to our staff */
188             if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
189                 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
190               new_encompasses.push_back (breakables[j]);
191         }
192       else
193         new_encompasses.push_back (g);
194     }
195
196   SCM encompass_scm = me->get_object ("encompass-objects");
197   if (Grob_array::unsmob (encompass_scm))
198     {
199       vector<Grob *> &arr =
200         unsmob_grob_array (encompass_scm)->array_reference ();
201       arr = new_encompasses;
202     }
203 }
204
205 Bezier
206 Slur::get_curve (Grob *me)
207 {
208   Bezier b;
209   int i = 0;
210   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
211        s = scm_cdr (s))
212     b.control_[i++] = ly_scm2offset (scm_car (s));
213
214   return b;
215 }
216
217 void
218 Slur::add_column (Grob *me, Grob *n)
219 {
220   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
221   add_bound_item (dynamic_cast<Spanner *> (me), n);
222 }
223
224 void
225 Slur::add_extra_encompass (Grob *me, Grob *n)
226 {
227   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
228 }
229
230 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
231 SCM
232 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
233 {
234   int start = robust_scm2int (start_scm, 0);
235   int end = robust_scm2int (end_scm, 0);
236   Grob *script = unsmob_grob (grob);
237   Grob *slur = unsmob_grob (script->get_object ("slur"));
238   if (!slur)
239     return offset_scm;
240
241   SCM avoid = script->get_property ("avoid-slur");
242   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
243     return offset_scm;
244
245   Real offset = robust_scm2double (offset_scm, 0.0);
246   Direction dir = get_grob_direction (script);
247   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
248 }
249
250 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
251 SCM
252 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
253 {
254   Grob *script = unsmob_grob (grob);
255   Grob *slur = unsmob_grob (script->get_object ("slur")); 
256
257   if (!slur)
258     return offset_scm;
259
260   SCM avoid = script->get_property ("avoid-slur");
261   if (avoid != ly_symbol2scm ("outside")
262       && avoid != ly_symbol2scm ("around"))
263     return offset_scm;
264   
265   Direction dir = get_grob_direction (script);
266   if (dir == CENTER)
267     return offset_scm;
268
269   Grob *cx = script->common_refpoint (slur, X_AXIS);
270   Grob *cy = script->common_refpoint (slur, Y_AXIS);
271
272   Bezier curve = Slur::get_curve (slur);
273
274   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
275                            slur->relative_coordinate (cy, Y_AXIS)));
276
277   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
278   Interval xext = robust_relative_extent (script, cx, X_AXIS);
279
280   Real offset = robust_scm2double (offset_scm, 0);
281   yext.translate (offset);
282   
283   /* FIXME: slur property, script property?  */
284   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
285                                          0.0);
286   yext.widen (slur_padding);
287
288   Real EPS = 1e-3;
289   Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
290   bool consider[] = { false, false, false };
291   Real ys[] = {0, 0, 0};
292   bool do_shift = false;
293   
294   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
295     {
296       Real x = xext.linear_combination ((Direction) d);
297       consider[k] = bezext.contains (x);
298
299       if (consider[k])
300         {
301           ys[k]
302             = (fabs (bezext[LEFT] - x) < EPS)
303             ? curve.control_[0][Y_AXIS]
304             : ((fabs (bezext[RIGHT] - x) < EPS)
305                ? curve.control_[3][Y_AXIS]
306                : curve.get_other_coordinate (X_AXIS, x));
307
308           /* Request shift if slur is contained script's Y, or if
309              script is inside slur and avoid == outside.  */
310           if (yext.contains (ys[k])
311               || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
312             do_shift = true;
313         }
314     }
315
316   Real avoidance_offset = 0.0;
317   if (do_shift)
318     {
319       for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
320         if (consider[k])
321           avoidance_offset = dir * (max (dir * avoidance_offset,
322                                          dir * (ys[k] - yext[-dir] + dir * slur_padding)));
323     }
324   return scm_from_double (offset + avoidance_offset);
325 }
326
327 /*
328  * Used by Slur_engraver:: and Phrasing_slur_engraver::
329  */
330 void
331 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
332                                           vector<Grob*> &slurs,
333                                           vector<Grob*> &end_slurs)
334 {
335   if (slurs.empty () && end_slurs.empty ())
336     return;
337   
338   Grob *e = info.grob ();
339   SCM avoid = e->get_property ("avoid-slur");
340   if (Tie::has_interface (e)
341       || avoid == ly_symbol2scm ("inside"))
342     {
343       for (vsize i = slurs.size (); i--;)
344         add_extra_encompass (slurs[i], e);
345       for (vsize i = end_slurs.size (); i--;)
346         add_extra_encompass (end_slurs[i], e);
347     }
348   else if (avoid == ly_symbol2scm ("outside")
349            || avoid == ly_symbol2scm ("around"))
350     {
351       Grob *slur;
352       if (end_slurs.size () && !slurs.size ())
353         slur = end_slurs[0];
354       else
355         slur = slurs[0];
356
357       if (slur)
358         {
359           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
360           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
361           e->set_object ("slur", slur->self_scm ());
362         }
363     }
364   else
365     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
366                     e->name().c_str ()));
367 }
368
369 /*
370   A callback that will be chained together with the original cross-staff
371   value of a grob that is placed 'outside or 'around a slur. This just says
372   that any grob becomes cross-staff if it is placed 'outside or 'around a
373   cross-staff slur.
374 */
375 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
376 SCM
377 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
378 {
379   if (previous == SCM_BOOL_T)
380     return previous;
381
382   Grob *me = unsmob_grob (smob);
383   Grob *slur = unsmob_grob (me->get_object ("slur"));
384
385   if (!slur)
386     return SCM_BOOL_F;
387   return slur->get_property ("cross-staff");
388 }
389
390 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
391 SCM
392 Slur::calc_cross_staff (SCM smob)
393 {
394   Grob *me = unsmob_grob (smob);
395
396   extract_grob_set (me, "note-columns", cols);
397   extract_grob_set (me, "encompass-objects", extras);
398
399   for (vsize i = 0; i < cols.size (); i++)
400     {
401       if (Grob *s = Note_column::get_stem (cols[i]))
402         if (to_boolean (s->get_property ("cross-staff")))
403           return SCM_BOOL_T;
404     }
405
406   /* the separation items are dealt with in replace_breakable_encompass_objects
407      so we can ignore them here */
408   vector<Grob*> non_sep_extras;
409   for (vsize i = 0; i < extras.size (); i++)
410     if (!Separation_item::has_interface (extras[i]))
411       non_sep_extras.push_back (extras[i]);
412
413   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
414   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
415
416   return scm_from_bool (common != me->get_parent (Y_AXIS));
417 }
418
419 ADD_INTERFACE (Slur,
420                "A slur.",
421                
422                /* properties */
423                "annotation "
424                "avoid-slur "    /* UGH. */
425                "control-points "
426                "dash-definition "
427                "details "
428                "direction "
429                "eccentricity "
430                "encompass-objects "
431                "height-limit "
432                "inspect-quants "
433                "inspect-index "
434                "line-thickness "
435                "note-columns "
436                "positions "
437                "quant-score "
438                "ratio "
439                "thickness "
440                );
441