]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Merge commit '25c91d5'
[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 "pointer-group-interface.hh"
18 #include "lookup.hh"
19 #include "main.hh"              // DEBUG_SLUR_SCORING
20 #include "note-column.hh"
21 #include "output-def.hh"
22 #include "spanner.hh"
23 #include "staff-symbol-referencer.hh"
24 #include "stem.hh"
25 #include "text-interface.hh"
26 #include "tie.hh"
27 #include "warn.hh"
28 #include "slur-scoring.hh"
29 #include "separation-item.hh"
30 #include "international.hh"
31
32
33
34 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
35 SCM
36 Slur::calc_direction (SCM smob)
37 {
38   Grob *me = unsmob_grob (smob);
39   extract_grob_set (me, "note-columns", encompasses);
40
41   if (encompasses.empty ())
42     {
43       me->suicide ();
44       return SCM_BOOL_F;
45     }
46
47   Direction d = DOWN;
48   for (vsize i = 0; i < encompasses.size (); i++)
49     {
50       if (Note_column::dir (encompasses[i]) < 0)
51         {
52           d = UP;
53           break;
54         }
55     }
56   return scm_from_int (d);
57 }
58
59 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
60 SCM
61 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
62 {
63   Grob *me = unsmob_grob (smob);
64   int start = scm_to_int (start_scm);
65   int end = scm_to_int (end_scm);
66   Real height = robust_scm2double (me->get_property ("height-limit"), 2.0);
67
68   extract_grob_set (me, "note-columns", encompasses);
69   Interval ret;
70
71   Grob *parent = me->get_parent (Y_AXIS);
72   if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
73     /* this could happen if, for example, we are a cross-staff slur.
74        in this case, we want to be ignored */
75     return ly_interval2scm (Interval ());
76
77   for (vsize i = 0; i < encompasses.size (); i++)
78     {
79       Interval d = encompasses[i]->pure_height (parent, start, end);
80       if (!d.is_empty ())
81         ret.unite (d);
82     }
83
84   ret.widen (height * 0.5);
85   return ly_interval2scm (ret);
86 }
87
88 MAKE_SCHEME_CALLBACK (Slur, height, 1);
89 SCM
90 Slur::height (SCM smob)
91 {
92   Grob *me = unsmob_grob (smob);
93
94   // FIXME uncached
95   Stencil *m = me->get_stencil ();
96   return m ? ly_interval2scm (m->extent (Y_AXIS))
97     : ly_interval2scm (Interval ());
98 }
99
100 /*
101   Ugh should have dash-length + dash-period
102 */
103 MAKE_SCHEME_CALLBACK (Slur, print, 1);
104 SCM
105 Slur::print (SCM smob)
106 {
107   Grob *me = unsmob_grob (smob);
108   extract_grob_set (me, "note-columns", encompasses);
109   if (encompasses.empty ())
110     {
111       me->suicide ();
112       return SCM_EOL;
113     }
114
115   Real staff_thick = Staff_symbol_referencer::line_thickness (me);
116   Real base_thick = staff_thick
117     * robust_scm2double (me->get_property ("thickness"), 1);
118   Real line_thick = staff_thick
119     * robust_scm2double (me->get_property ("line-thickness"), 1);
120
121   Bezier one = get_curve (me);
122   Stencil a;
123
124   /*
125     TODO: replace dashed with generic property.
126   */
127   SCM p = me->get_property ("dash-period");
128   SCM f = me->get_property ("dash-fraction");
129   if (scm_is_number (p) && scm_is_number (f))
130     a = Lookup::dashed_slur (one, line_thick, robust_scm2double (p, 1.0),
131                              robust_scm2double (f, 0));
132   else
133     a = Lookup::slur (one,
134                       get_grob_direction (me) * base_thick,
135                       line_thick);
136
137 #if DEBUG_SLUR_SCORING
138   SCM quant_score = me->get_property ("quant-score");
139
140   if (to_boolean (me->layout ()
141                   ->lookup_variable (ly_symbol2scm ("debug-slur-scoring")))
142       && scm_is_string (quant_score))
143     {
144       string str;
145       SCM properties = Font_interface::text_font_alist_chain (me);
146
147
148       if (!scm_is_number (me->get_property ("font-size")))
149         properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
150                              properties);
151       
152       Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
153                                     (me->layout ()->self_scm (), properties,
154                                      quant_score));
155       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
156     }
157 #endif
158
159   return a.smobbed_copy ();
160 }
161
162
163 /*
164   it would be better to do this at engraver level, but that is
165   fragile, as the breakabl items are generated on staff level, at
166   which point slur starts and ends have to be tracked
167 */
168 void
169 Slur::replace_breakable_encompass_objects (Grob *me)
170 {
171   extract_grob_set (me, "encompass-objects", extra_objects);
172   vector<Grob *> new_encompasses;
173
174   for (vsize i = 0; i < extra_objects.size (); i++)
175     {
176       Grob *g = extra_objects[i];
177       
178       if (Separation_item::has_interface (g))
179         {
180           extract_grob_set (g, "elements", breakables);
181           for (vsize j = 0; j < breakables.size (); j++)
182             /* if we encompass a separation-item that spans multiple staves,
183                we filter out the grobs that don't belong to our staff */
184             if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
185                 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
186               new_encompasses.push_back (breakables[j]);
187         }
188       else
189         new_encompasses.push_back (g);
190     }
191
192   SCM encompass_scm = me->get_object ("encompass-objects");
193   if (Grob_array::unsmob (encompass_scm))
194     {
195       vector<Grob *> &arr =
196         unsmob_grob_array (encompass_scm)->array_reference ();
197       arr = new_encompasses;
198     }
199 }
200
201 Bezier
202 Slur::get_curve (Grob *me)
203 {
204   Bezier b;
205   int i = 0;
206   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
207        s = scm_cdr (s))
208     b.control_[i++] = ly_scm2offset (scm_car (s));
209
210   return b;
211 }
212
213 void
214 Slur::add_column (Grob *me, Grob *n)
215 {
216   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
217   add_bound_item (dynamic_cast<Spanner *> (me), n);
218 }
219
220 void
221 Slur::add_extra_encompass (Grob *me, Grob *n)
222 {
223   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
224 }
225
226 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
227 SCM
228 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
229 {
230   int start = robust_scm2int (start_scm, 0);
231   int end = robust_scm2int (end_scm, 0);
232   Grob *script = unsmob_grob (grob);
233   Grob *slur = unsmob_grob (script->get_object ("slur"));
234   if (!slur)
235     return offset_scm;
236
237   SCM avoid = script->get_property ("avoid-slur");
238   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
239     return offset_scm;
240
241   Real offset = robust_scm2double (offset_scm, 0.0);
242   Direction dir = get_grob_direction (script);
243   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
244 }
245
246 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
247 SCM
248 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
249 {
250   Grob *script = unsmob_grob (grob);
251   Grob *slur = unsmob_grob (script->get_object ("slur")); 
252
253   if (!slur)
254     return offset_scm;
255
256   SCM avoid = script->get_property ("avoid-slur");
257   if (avoid != ly_symbol2scm ("outside")
258       && avoid != ly_symbol2scm ("around"))
259     return offset_scm;
260   
261   Direction dir = get_grob_direction (script);
262   if (dir == CENTER)
263     return offset_scm;
264
265   Grob *cx = script->common_refpoint (slur, X_AXIS);
266   Grob *cy = script->common_refpoint (slur, Y_AXIS);
267
268   Bezier curve = Slur::get_curve (slur);
269
270   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
271                            slur->relative_coordinate (cy, Y_AXIS)));
272
273   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
274   Interval xext = robust_relative_extent (script, cx, X_AXIS);
275
276   Real offset = robust_scm2double (offset_scm, 0);
277   yext.translate (offset);
278   
279   /* FIXME: slur property, script property?  */
280   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
281                                          0.0);
282   yext.widen (slur_padding);
283
284   Real EPS = 1e-3;
285   Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
286   bool consider[] = { false, false, false };
287   Real ys[] = {0, 0, 0};
288   bool do_shift = false;
289   
290   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
291     {
292       Real x = xext.linear_combination ((Direction) d);
293       consider[k] = bezext.contains (x);
294
295       if (consider[k])
296         {
297           ys[k]
298             = (fabs (bezext[LEFT] - x) < EPS)
299             ? curve.control_[0][Y_AXIS]
300             : ((fabs (bezext[RIGHT] - x) < EPS)
301                ? curve.control_[3][Y_AXIS]
302                : curve.get_other_coordinate (X_AXIS, x));
303
304           /* Request shift if slur is contained script's Y, or if
305              script is inside slur and avoid == outside.  */
306           if (yext.contains (ys[k])
307               || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
308             do_shift = true;
309         }
310     }
311
312   Real avoidance_offset = 0.0;
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)));
317   
318   return scm_from_double (offset + avoidance_offset);
319 }
320
321 /*
322  * Used by Slur_engraver:: and Phrasing_slur_engraver::
323  */
324 void
325 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
326                                           vector<Grob*> &slurs,
327                                           vector<Grob*> &end_slurs)
328 {
329   if (slurs.empty () && end_slurs.empty ())
330     return;
331   
332   Grob *e = info.grob ();
333   SCM avoid = e->get_property ("avoid-slur");
334   if (Tie::has_interface (e)
335       || avoid == ly_symbol2scm ("inside"))
336     {
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);
341     }
342   else if (avoid == ly_symbol2scm ("outside")
343            || avoid == ly_symbol2scm ("around"))
344     {
345       Grob *slur;
346       if (end_slurs.size () && !slurs.size ())
347         slur = end_slurs[0];
348       else
349         slur = slurs[0];
350
351       if (slur)
352         {
353           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
354           e->set_object ("slur", slur->self_scm ());
355         }
356     }
357   else
358     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
359                     e->name().c_str ()));
360 }
361
362 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
363 SCM
364 Slur::calc_cross_staff (SCM smob)
365 {
366   Grob *me = unsmob_grob (smob);
367
368   extract_grob_set (me, "note-columns", cols);
369   extract_grob_set (me, "encompass-objects", extras);
370
371   /* the separation items are dealt with in replace_breakable_encompass_objects
372      so we can ignore them here */
373   vector<Grob*> non_sep_extras;
374   for (vsize i = 0; i < extras.size (); i++)
375     if (!Separation_item::has_interface (extras[i]))
376       non_sep_extras.push_back (extras[i]);
377
378   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
379   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
380
381   return scm_from_bool (common != me->get_parent (Y_AXIS));
382 }
383
384 ADD_INTERFACE (Slur,
385                
386                "A slur",
387                
388                /* properties */
389                "avoid-slur "    /* UGH. */
390                "control-points "
391                "dash-fraction "
392                "dash-period "
393                "details "
394                "direction "
395                "eccentricity "
396                "encompass-objects "
397                "height-limit "
398                "inspect-quants "
399                "inspect-index "
400                "line-thickness "
401                "note-columns "
402                "positions "
403                "quant-score "
404                "ratio "
405                "thickness "
406                );
407