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