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