]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Make sure cross-staff tuplet brackets belong to the right staff.
[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 (breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
183               new_encompasses.push_back (breakables[j]);
184         }
185       else
186         new_encompasses.push_back (g);
187     }
188
189   SCM encompass_scm = me->get_object ("encompass-objects");
190   if (Grob_array::unsmob (encompass_scm))
191     {
192       vector<Grob *> &arr =
193         unsmob_grob_array (encompass_scm)->array_reference ();
194       arr = new_encompasses;
195     }
196 }
197
198 Bezier
199 Slur::get_curve (Grob *me)
200 {
201   Bezier b;
202   int i = 0;
203   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
204        s = scm_cdr (s))
205     b.control_[i++] = ly_scm2offset (scm_car (s));
206
207   return b;
208 }
209
210 void
211 Slur::add_column (Grob *me, Grob *n)
212 {
213   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
214   add_bound_item (dynamic_cast<Spanner *> (me), n);
215 }
216
217 void
218 Slur::add_extra_encompass (Grob *me, Grob *n)
219 {
220   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
221 }
222
223 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
224 SCM
225 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
226 {
227   int start = robust_scm2int (start_scm, 0);
228   int end = robust_scm2int (end_scm, 0);
229   Grob *script = unsmob_grob (grob);
230   Grob *slur = unsmob_grob (script->get_object ("slur"));
231   if (!slur)
232     return offset_scm;
233
234   SCM avoid = script->get_property ("avoid-slur");
235   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
236     return offset_scm;
237
238   Real offset = robust_scm2double (offset_scm, 0.0);
239   Direction dir = get_grob_direction (script);
240   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
241 }
242
243 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
244 SCM
245 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
246 {
247   Grob *script = unsmob_grob (grob);
248   Grob *slur = unsmob_grob (script->get_object ("slur")); 
249
250   if (!slur)
251     return offset_scm;
252
253   SCM avoid = script->get_property ("avoid-slur");
254   if (avoid != ly_symbol2scm ("outside")
255       && avoid != ly_symbol2scm ("around"))
256     return offset_scm;
257   
258   Direction dir = get_grob_direction (script);
259   if (dir == CENTER)
260     return offset_scm;
261
262   Grob *cx = script->common_refpoint (slur, X_AXIS);
263   Grob *cy = script->common_refpoint (slur, Y_AXIS);
264
265   Bezier curve = Slur::get_curve (slur);
266
267   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
268                            slur->relative_coordinate (cy, Y_AXIS)));
269
270   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
271   Interval xext = robust_relative_extent (script, cx, X_AXIS);
272
273   Real offset = robust_scm2double (offset_scm, 0);
274   yext.translate (offset);
275   
276   /* FIXME: slur property, script property?  */
277   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
278                                          0.0);
279   yext.widen (slur_padding);
280
281   Real EPS = 1e-3;
282   Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
283   bool consider[] = { false, false, false };
284   Real ys[] = {0, 0, 0};
285   bool do_shift = false;
286   
287   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
288     {
289       Real x = xext.linear_combination ((Direction) d);
290       consider[k] = bezext.contains (x);
291
292       if (consider[k])
293         {
294           ys[k]
295             = (fabs (bezext[LEFT] - x) < EPS)
296             ? curve.control_[0][Y_AXIS]
297             : ((fabs (bezext[RIGHT] - x) < EPS)
298                ? curve.control_[3][Y_AXIS]
299                : curve.get_other_coordinate (X_AXIS, x));
300
301           /* Request shift if slur is contained script's Y, or if
302              script is inside slur and avoid == outside.  */
303           if (yext.contains (ys[k])
304               || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
305             do_shift = true;
306         }
307     }
308
309   Real avoidance_offset = 0.0;
310   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
311     if (consider[k]) 
312       avoidance_offset = dir * (max (dir * avoidance_offset,
313                                      dir * (ys[k] - yext[-dir] + dir * slur_padding)));
314   
315   return scm_from_double (offset + avoidance_offset);
316 }
317
318 /*
319  * Used by Slur_engraver:: and Phrasing_slur_engraver::
320  */
321 void
322 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
323                                           vector<Grob*> &slurs,
324                                           vector<Grob*> &end_slurs)
325 {
326   if (slurs.empty () && end_slurs.empty ())
327     return;
328   
329   Grob *e = info.grob ();
330   SCM avoid = e->get_property ("avoid-slur");
331   if (Tie::has_interface (e)
332       || avoid == ly_symbol2scm ("inside"))
333     {
334       for (vsize i = slurs.size (); i--;)
335         add_extra_encompass (slurs[i], e);
336       for (vsize i = end_slurs.size (); i--;)
337         add_extra_encompass (end_slurs[i], e);
338     }
339   else if (avoid == ly_symbol2scm ("outside")
340            || avoid == ly_symbol2scm ("around"))
341     {
342       Grob *slur;
343       if (end_slurs.size () && !slurs.size ())
344         slur = end_slurs[0];
345       else
346         slur = slurs[0];
347
348       if (slur)
349         {
350           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
351           e->set_object ("slur", slur->self_scm ());
352         }
353     }
354   else
355     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
356                     e->name().c_str ()));
357 }
358
359 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
360 SCM
361 Slur::calc_cross_staff (SCM smob)
362 {
363   Grob *me = unsmob_grob (smob);
364   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
365   assert (staff); // delete me
366   extract_grob_set (me, "note-columns", cols);
367
368   for (vsize i = 0; i < cols.size (); i++)
369     if (Staff_symbol_referencer::get_staff_symbol (cols[i]) != staff)
370       return SCM_BOOL_T;
371   return SCM_BOOL_F;
372 }
373
374 ADD_INTERFACE (Slur,
375                
376                "A slur",
377                
378                /* properties */
379                "avoid-slur "    /* UGH. */
380                "control-points "
381                "dash-fraction "
382                "dash-period "
383                "details "
384                "direction "
385                "eccentricity "
386                "encompass-objects "
387                "height-limit "
388                "inspect-quants "
389                "inspect-index "
390                "line-thickness "
391                "note-columns "
392                "positions "
393                "quant-score "
394                "ratio "
395                "thickness "
396                );
397