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