]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Merge branch 'master' into lilypond/translation
[lilypond.git] / lily / slur.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1996--2011 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   // The +0.5 comes from the fact that we try to place a slur
97   // 0.5 staff spaces from the note-head.
98   // (see Slur_score_state.get_base_attachments ())
99   ret.widen (height * 0.5 + 0.5);
100   return ly_interval2scm (ret);
101 }
102
103 MAKE_SCHEME_CALLBACK (Slur, height, 1);
104 SCM
105 Slur::height (SCM smob)
106 {
107   Grob *me = unsmob_grob (smob);
108
109   // FIXME uncached
110   Stencil *m = me->get_stencil ();
111   return m ? ly_interval2scm (m->extent (Y_AXIS))
112     : ly_interval2scm (Interval ());
113 }
114
115 MAKE_SCHEME_CALLBACK (Slur, print, 1);
116 SCM
117 Slur::print (SCM smob)
118 {
119   Grob *me = unsmob_grob (smob);
120   extract_grob_set (me, "note-columns", encompasses);
121   if (encompasses.empty ())
122     {
123       me->suicide ();
124       return SCM_EOL;
125     }
126
127   Real staff_thick = Staff_symbol_referencer::line_thickness (me);
128   Real base_thick = staff_thick
129     * robust_scm2double (me->get_property ("thickness"), 1);
130   Real line_thick = staff_thick
131     * robust_scm2double (me->get_property ("line-thickness"), 1);
132
133   Bezier one = get_curve (me);
134   Stencil a;
135
136   SCM dash_definition = me->get_property ("dash-definition");
137   a = Lookup::slur (one,
138                     get_grob_direction (me) * base_thick,
139                     line_thick,
140                     dash_definition);
141
142 #if DEBUG_SLUR_SCORING
143   SCM annotation = me->get_property ("annotation");
144   if (!scm_is_string (annotation))
145     {
146       SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-slur-scoring"));
147       if (to_boolean (debug))
148         annotation = me->get_property ("quant-score");
149     }
150   
151   if (scm_is_string (annotation))
152     {
153       string str;
154       SCM properties = Font_interface::text_font_alist_chain (me);
155
156       if (!scm_is_number (me->get_property ("font-size")))
157         properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
158                              properties);
159       
160       Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
161                                     (me->layout ()->self_scm (), properties,
162                                      annotation));
163       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
164     }
165 #endif
166
167   return a.smobbed_copy ();
168 }
169
170
171 /*
172   it would be better to do this at engraver level, but that is
173   fragile, as the breakable items are generated on staff level, at
174   which point slur starts and ends have to be tracked
175 */
176 void
177 Slur::replace_breakable_encompass_objects (Grob *me)
178 {
179   extract_grob_set (me, "encompass-objects", extra_objects);
180   vector<Grob *> new_encompasses;
181
182   for (vsize i = 0; i < extra_objects.size (); i++)
183     {
184       Grob *g = extra_objects[i];
185       
186       if (Separation_item::has_interface (g))
187         {
188           extract_grob_set (g, "elements", breakables);
189           for (vsize j = 0; j < breakables.size (); j++)
190             /* if we encompass a separation-item that spans multiple staves,
191                we filter out the grobs that don't belong to our staff */
192             if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
193                 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
194               new_encompasses.push_back (breakables[j]);
195         }
196       else
197         new_encompasses.push_back (g);
198     }
199
200   SCM encompass_scm = me->get_object ("encompass-objects");
201   if (Grob_array::unsmob (encompass_scm))
202     {
203       vector<Grob *> &arr =
204         unsmob_grob_array (encompass_scm)->array_reference ();
205       arr = new_encompasses;
206     }
207 }
208
209 Bezier
210 Slur::get_curve (Grob *me)
211 {
212   Bezier b;
213   int i = 0;
214   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
215        s = scm_cdr (s))
216     b.control_[i++] = ly_scm2offset (scm_car (s));
217
218   return b;
219 }
220
221 void
222 Slur::add_column (Grob *me, Grob *n)
223 {
224   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
225   add_bound_item (dynamic_cast<Spanner *> (me), n);
226 }
227
228 void
229 Slur::add_extra_encompass (Grob *me, Grob *n)
230 {
231   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
232 }
233
234 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
235 SCM
236 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
237 {
238   int start = robust_scm2int (start_scm, 0);
239   int end = robust_scm2int (end_scm, 0);
240   Grob *script = unsmob_grob (grob);
241   Grob *slur = unsmob_grob (script->get_object ("slur"));
242   if (!slur)
243     return offset_scm;
244
245   SCM avoid = script->get_property ("avoid-slur");
246   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
247     return offset_scm;
248
249   Real offset = robust_scm2double (offset_scm, 0.0);
250   Direction dir = get_grob_direction (script);
251   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
252 }
253
254 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
255 SCM
256 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
257 {
258   Grob *script = unsmob_grob (grob);
259   Grob *slur = unsmob_grob (script->get_object ("slur")); 
260
261   if (!slur)
262     return offset_scm;
263
264   SCM avoid = script->get_property ("avoid-slur");
265   if (avoid != ly_symbol2scm ("outside")
266       && avoid != ly_symbol2scm ("around"))
267     return offset_scm;
268   
269   Direction dir = get_grob_direction (script);
270   if (dir == CENTER)
271     return offset_scm;
272
273   Grob *cx = script->common_refpoint (slur, X_AXIS);
274   Grob *cy = script->common_refpoint (slur, Y_AXIS);
275
276   Bezier curve = Slur::get_curve (slur);
277
278   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
279                            slur->relative_coordinate (cy, Y_AXIS)));
280
281   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
282   Interval xext = robust_relative_extent (script, cx, X_AXIS);
283
284   Real offset = robust_scm2double (offset_scm, 0);
285   yext.translate (offset);
286   
287   /* FIXME: slur property, script property?  */
288   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
289                                          0.0);
290   yext.widen (slur_padding);
291
292   const Real EPS = 1e-3;
293   Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
294   bool consider[] = {false, false, false};
295   Real ys[] = {0, 0, 0};
296   bool do_shift = false;
297   
298   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
299     {
300       Real x = xext.linear_combination ((Direction) d);
301       consider[k] = bezext.contains (x);
302
303       if (consider[k])
304         {
305           ys[k]
306             = (fabs (bezext[LEFT] - x) < EPS)
307             ? curve.control_[0][Y_AXIS]
308             : ((fabs (bezext[RIGHT] - x) < EPS)
309                ? curve.control_[3][Y_AXIS]
310                : curve.get_other_coordinate (X_AXIS, x));
311
312           /* Request shift if slur is contained script's Y, or if
313              script is inside slur and avoid == outside.  */
314           if (yext.contains (ys[k])
315               || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
316             do_shift = true;
317         }
318     }
319
320   Real avoidance_offset = 0.0;
321   if (do_shift)
322     {
323       for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
324         if (consider[k])
325           avoidance_offset = dir * (max (dir * avoidance_offset,
326                                          dir * (ys[k] - yext[-dir] + dir * slur_padding)));
327     }
328   return scm_from_double (offset + avoidance_offset);
329 }
330
331 /*
332  * Used by Slur_engraver:: and Phrasing_slur_engraver::
333  */
334 void
335 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
336                                           vector<Grob*> &slurs,
337                                           vector<Grob*> &end_slurs)
338 {
339   if (slurs.empty () && end_slurs.empty ())
340     return;
341   
342   Grob *e = info.grob ();
343   SCM avoid = e->get_property ("avoid-slur");
344   if (Tie::has_interface (e)
345       || avoid == ly_symbol2scm ("inside"))
346     {
347       for (vsize i = slurs.size (); i--;)
348         add_extra_encompass (slurs[i], e);
349       for (vsize i = end_slurs.size (); i--;)
350         add_extra_encompass (end_slurs[i], e);
351     }
352   else if (avoid == ly_symbol2scm ("outside")
353            || avoid == ly_symbol2scm ("around"))
354     {
355       Grob *slur;
356       if (end_slurs.size () && !slurs.size ())
357         slur = end_slurs[0];
358       else
359         slur = slurs[0];
360
361       if (slur)
362         {
363           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
364           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm("cross-staff"));
365           e->set_object ("slur", slur->self_scm ());
366         }
367     }
368   else if (avoid != ly_symbol2scm ("ignore"))
369     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
370                     e->name().c_str ()));
371 }
372
373 /*
374   A callback that will be chained together with the original cross-staff
375   value of a grob that is placed 'outside or 'around a slur. This just says
376   that any grob becomes cross-staff if it is placed 'outside or 'around a
377   cross-staff slur.
378 */
379 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
380 SCM
381 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
382 {
383   if (previous == SCM_BOOL_T)
384     return previous;
385
386   Grob *me = unsmob_grob (smob);
387   Grob *slur = unsmob_grob (me->get_object ("slur"));
388
389   if (!slur)
390     return SCM_BOOL_F;
391   return slur->get_property ("cross-staff");
392 }
393
394 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
395 SCM
396 Slur::calc_cross_staff (SCM smob)
397 {
398   Grob *me = unsmob_grob (smob);
399
400   extract_grob_set (me, "note-columns", cols);
401   extract_grob_set (me, "encompass-objects", extras);
402
403   for (vsize i = 0; i < cols.size (); i++)
404     {
405       if (Grob *s = Note_column::get_stem (cols[i]))
406         if (to_boolean (s->get_property ("cross-staff")))
407           return SCM_BOOL_T;
408     }
409
410   /* the separation items are dealt with in replace_breakable_encompass_objects
411      so we can ignore them here */
412   vector<Grob*> non_sep_extras;
413   for (vsize i = 0; i < extras.size (); i++)
414     if (!Separation_item::has_interface (extras[i]))
415       non_sep_extras.push_back (extras[i]);
416
417   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
418   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
419
420   return scm_from_bool (common != me->get_parent (Y_AXIS));
421 }
422
423 ADD_INTERFACE (Slur,
424                "A slur."
425                "\n"
426                "The following properties may be set in the @code{details}"
427                " list.\n"
428                "\n"
429                "@table @code\n"
430                "@item region-size\n"
431                "Size of region (in staff spaces) for determining"
432                " potential endpoints in the Y direction.\n"
433                "@item head-encompass-penalty\n"
434                "Demerit to apply when note heads collide with a slur.\n"
435                "@item stem-encompass-penalty\n"
436                "Demerit to apply when stems collide with a slur.\n"
437                "@item closeness-factor\n"
438                "Additional demerit used when scoring encompasses.\n"
439                "@item edge-attraction-factor\n"
440                "Factor used to calculate the demerit for distances"
441                " between slur endpoints and their corresponding base"
442                " attachments.\n"
443                "@item same-slope-penalty\n"
444                "Demerit for slurs with attachment points that are"
445                " horizontally aligned.\n"
446                "@item steeper-slope-factor\n"
447                "Factor used to calculate demerit only if this slur is"
448                " not broken.\n"
449                "@item non-horizontal-penalty\n"
450                "Demerit for slurs with attachment points that are not"
451                " horizontally aligned.\n"
452                "@item max-slope\n"
453                "The maximum slope allowed for this slur.\n"
454                "@item max-slope-factor\n"
455                "Factor that calculates demerit based on the max slope.\n"
456                "@item free-head-distance\n"
457                "The amount of vertical free space that must exist"
458                " between a slur and note heads.\n"
459                "@item absolute-closeness-measure\n"
460                "Factor to calculate demerit for variance between a note"
461                " head and slur.\n"
462                "@item extra-object-collision-penalty\n"
463                "Factor to calculate demerit for extra objects that the"
464                " slur encompasses, including accidentals, fingerings, and"
465                " tuplet numbers.\n"
466                "@item accidental-collision\n"
467                "Factor to calculate demerit for @code{Accidental} objects"
468                " that the slur encompasses.  This property value replaces"
469                " the value of @code{extra-object-collision-penalty}.\n"
470                "@item extra-encompass-free-distance\n"
471                "The amount of vertical free space that must exist"
472                " between a slur and various objects it encompasses,"
473                " including accidentals, fingerings, and tuplet numbers.\n"
474                "@item extra-encompass-collision-distance\n"
475                "This detail is currently unused.\n"
476                "@item head-slur-distance-factor\n"
477                "Factor to calculate demerit for variance between a note"
478                " head and slur.\n"
479                "@item head-slur-distance-max-ratio\n"
480                "The maximum value for the ratio of distance between a"
481                " note head and slur.\n"
482                "@item free-slur-distance\n"
483                "The amount of vertical free space that must exist"
484                " between adjacent slurs.  This subproperty only works"
485                " for @code{PhrasingSlur}.\n"
486                "@item edge-slope-exponent\n"
487                "Factor used to calculate the demerit for the slope of"
488                " a slur near its endpoints; a larger value yields a"
489                " larger demerit.\n"
490                "@end table\n",
491                
492                /* properties */
493                "annotation "
494                "avoid-slur "    /* UGH. */
495                "control-points "
496                "dash-definition "
497                "details "
498                "direction "
499                "eccentricity "
500                "encompass-objects "
501                "height-limit "
502                "inspect-quants "
503                "inspect-index "
504                "line-thickness "
505                "note-columns "
506                "positions "
507                "quant-score "
508                "ratio "
509                "thickness "
510                );
511