]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Merge remote branch 'origin/release/unstable' into HEAD
[lilypond.git] / lily / slur.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1996--2012 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 "skyline-pair.hh"
36 #include "staff-symbol-referencer.hh"
37 #include "stem.hh"
38 #include "text-interface.hh"
39 #include "tie.hh"
40 #include "warn.hh"
41 #include "slur-scoring.hh"
42 #include "separation-item.hh"
43 #include "international.hh"
44
45 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
46 SCM
47 Slur::calc_direction (SCM smob)
48 {
49   Grob *me = unsmob_grob (smob);
50   extract_grob_set (me, "note-columns", encompasses);
51
52   if (encompasses.empty ())
53     {
54       me->suicide ();
55       return SCM_BOOL_F;
56     }
57
58   Direction d = DOWN;
59   for (vsize i = 0; i < encompasses.size (); i++)
60     {
61       if (Note_column::dir (encompasses[i]) < 0)
62         {
63           d = UP;
64           break;
65         }
66     }
67   return scm_from_int (d);
68 }
69
70 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
71 SCM
72 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
73 {
74   /*
75     Note that this estimation uses a rote add-on of 0.5 to the
76     highest encompassed note-head for a slur estimate.  This is,
77     in most cases, shorter than the actual slur.
78
79     Ways to improve this could include:
80     -- adding extra height for scripts that avoid slurs on the inside
81     -- adding extra height for the "bulge" in a slur above a note head
82   */
83   Grob *me = unsmob_grob (smob);
84   int start = scm_to_int (start_scm);
85   int end = scm_to_int (end_scm);
86   Direction dir = get_grob_direction (me);
87
88   extract_grob_set (me, "note-columns", encompasses);
89   Interval ret;
90   ret.set_empty ();
91
92   Grob *parent = me->get_parent (Y_AXIS);
93   Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
94   if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
95     /* this could happen if, for example, we are a cross-staff slur.
96        in this case, we want to be ignored */
97     return ly_interval2scm (Interval ());
98
99   for (vsize i = 0; i < encompasses.size (); i++)
100     {
101       Interval d = encompasses[i]->pure_height (parent, start, end);
102       if (!d.is_empty ())
103         {
104           for (DOWN_and_UP (downup))
105             ret.add_point (d[dir]);
106
107           if (extremal_heights[LEFT] == infinity_f)
108             extremal_heights[LEFT] = d[dir];
109           extremal_heights[RIGHT] = d[dir];
110         }
111     }
112
113   if (ret.is_empty ())
114     return ly_interval2scm (Interval ());
115
116   Interval extremal_span;
117   extremal_span.set_empty ();
118   for (LEFT_and_RIGHT (d))
119     extremal_span.add_point (extremal_heights[d]);
120   ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
121
122   /*
123     The +0.5 comes from the fact that we try to place a slur
124     0.5 staff spaces from the note-head.
125     (see Slur_score_state.get_base_attachments ())
126   */
127   ret += 0.5 * dir;
128   return ly_interval2scm (ret);
129 }
130
131 MAKE_SCHEME_CALLBACK (Slur, height, 1);
132 SCM
133 Slur::height (SCM smob)
134 {
135   Grob *me = unsmob_grob (smob);
136
137   // FIXME uncached
138   Stencil *m = me->get_stencil ();
139   return m ? ly_interval2scm (m->extent (Y_AXIS))
140          : ly_interval2scm (Interval ());
141 }
142
143 MAKE_SCHEME_CALLBACK (Slur, print, 1);
144 SCM
145 Slur::print (SCM smob)
146 {
147   Grob *me = unsmob_grob (smob);
148   extract_grob_set (me, "note-columns", encompasses);
149   if (encompasses.empty ())
150     {
151       me->suicide ();
152       return SCM_EOL;
153     }
154
155   Real staff_thick = Staff_symbol_referencer::line_thickness (me);
156   Real base_thick = staff_thick
157                     * robust_scm2double (me->get_property ("thickness"), 1);
158   Real line_thick = staff_thick
159                     * robust_scm2double (me->get_property ("line-thickness"), 1);
160
161   Bezier one = get_curve (me);
162   Stencil a;
163
164   SCM dash_definition = me->get_property ("dash-definition");
165   a = Lookup::slur (one,
166                     get_grob_direction (me) * base_thick,
167                     line_thick,
168                     dash_definition);
169
170 #if DEBUG_SLUR_SCORING
171   SCM annotation = me->get_property ("annotation");
172   if (scm_is_string (annotation))
173     {
174       string str;
175       SCM properties = Font_interface::text_font_alist_chain (me);
176
177       if (!scm_is_number (me->get_property ("font-size")))
178         properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
179                                properties);
180
181       Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
182                                     (me->layout ()->self_scm (), properties,
183                                      annotation));
184       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
185     }
186 #endif
187
188   return a.smobbed_copy ();
189 }
190
191 /*
192   it would be better to do this at engraver level, but that is
193   fragile, as the breakable items are generated on staff level, at
194   which point slur starts and ends have to be tracked
195 */
196 void
197 Slur::replace_breakable_encompass_objects (Grob *me)
198 {
199   extract_grob_set (me, "encompass-objects", extra_objects);
200   vector<Grob *> new_encompasses;
201
202   for (vsize i = 0; i < extra_objects.size (); i++)
203     {
204       Grob *g = extra_objects[i];
205
206       if (Separation_item::has_interface (g))
207         {
208           extract_grob_set (g, "elements", breakables);
209           for (vsize j = 0; j < breakables.size (); j++)
210             /* if we encompass a separation-item that spans multiple staves,
211                we filter out the grobs that don't belong to our staff */
212             if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
213                 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
214               new_encompasses.push_back (breakables[j]);
215         }
216       else
217         new_encompasses.push_back (g);
218     }
219
220   SCM encompass_scm = me->get_object ("encompass-objects");
221   if (Grob_array::unsmob (encompass_scm))
222     {
223       vector<Grob *> &arr
224         = unsmob_grob_array (encompass_scm)->array_reference ();
225       arr = new_encompasses;
226     }
227 }
228
229 Bezier
230 Slur::get_curve (Grob *me)
231 {
232   Bezier b;
233   int i = 0;
234   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
235        s = scm_cdr (s))
236     b.control_[i++] = ly_scm2offset (scm_car (s));
237
238   return b;
239 }
240
241 void
242 Slur::add_column (Grob *me, Grob *n)
243 {
244   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
245   add_bound_item (dynamic_cast<Spanner *> (me), n);
246 }
247
248 void
249 Slur::add_extra_encompass (Grob *me, Grob *n)
250 {
251   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
252 }
253
254 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
255 SCM
256 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
257 {
258   int start = robust_scm2int (start_scm, 0);
259   int end = robust_scm2int (end_scm, 0);
260   Grob *script = unsmob_grob (grob);
261   Grob *slur = unsmob_grob (script->get_object ("slur"));
262   if (!slur)
263     return offset_scm;
264
265   SCM avoid = script->get_property ("avoid-slur");
266   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
267     return offset_scm;
268
269   Real offset = robust_scm2double (offset_scm, 0.0);
270   Direction dir = get_grob_direction (script);
271   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
272 }
273
274 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
275 SCM
276 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
277 {
278   Grob *script = unsmob_grob (grob);
279   Grob *slur = unsmob_grob (script->get_object ("slur"));
280
281   if (!slur)
282     return offset_scm;
283
284   SCM avoid = script->get_property ("avoid-slur");
285   if (avoid != ly_symbol2scm ("outside")
286       && avoid != ly_symbol2scm ("around"))
287     return offset_scm;
288
289   Direction dir = get_grob_direction (script);
290   if (dir == CENTER)
291     return offset_scm;
292
293   Grob *cx = script->common_refpoint (slur, X_AXIS);
294   Grob *cy = script->common_refpoint (slur, Y_AXIS);
295
296   Bezier curve = Slur::get_curve (slur);
297
298   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
299                            slur->relative_coordinate (cy, Y_AXIS)));
300
301   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
302   Interval xext = robust_relative_extent (script, cx, X_AXIS);
303   Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
304
305   /*
306     cannot use is_empty because some 0-extent scripts
307     come up with TabStaffs.
308   */
309   if (xext.length () <= 0 || yext.length () <= 0)
310     return offset_scm;
311
312   bool contains = false;
313   for (LEFT_and_RIGHT (d))
314     contains |= slur_wid.contains (xext[d]);
315
316   if (!contains)
317     return offset_scm;
318
319   Real offset = robust_scm2double (offset_scm, 0);
320   yext.translate (offset);
321
322   /* FIXME: slur property, script property?  */
323   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
324                                          0.0);
325   yext.widen (slur_padding);
326
327   Interval exts[] = {xext, yext};
328   bool do_shift = false;
329   Real EPS = 1.0e-5;
330   if (avoid == ly_symbol2scm ("outside"))
331     {
332       for (LEFT_and_RIGHT (d))
333         {
334           Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
335           Real y = curve.get_other_coordinate (X_AXIS, x);
336           do_shift = y == minmax (dir, yext[-dir], y);
337           if (do_shift)
338             break;
339         }
340     }
341   else
342     {
343       for (int a = X_AXIS; a < NO_AXES; a++)
344         {
345           for (LEFT_and_RIGHT (d))
346             {
347               vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
348               for (vsize i = 0; i < coords.size (); i++)
349                 {
350                   do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
351                   if (do_shift)
352                     break;
353                 }
354               if (do_shift)
355                 break;
356             }
357           if (do_shift)
358             break;
359         }
360     }
361
362   Real avoidance_offset = do_shift ? curve.minmax (X_AXIS, max (xext[LEFT], curve.control_[0][X_AXIS] + EPS), min (xext[RIGHT], curve.control_[3][X_AXIS] - EPS), dir) - yext[-dir] : 0.0;
363
364   return scm_from_double (offset + avoidance_offset);
365 }
366
367 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, vertical_skylines, 1, 0, "");
368 SCM
369 Slur::vertical_skylines (SCM smob)
370 {
371   Grob *me = unsmob_grob (smob);
372   vector<Box> boxes;
373
374   if (!me)
375     return Skyline_pair (boxes, 0.0, X_AXIS).smobbed_copy ();
376
377   Bezier curve = Slur::get_curve (me);
378   vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
379   for (vsize i = 0; i < box_count; i++)
380     {
381       Box b;
382       b.add_point (curve.curve_point (i * 1.0 / box_count));
383       b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
384       boxes.push_back (b);
385     }
386
387   return Skyline_pair (boxes, 0.0, X_AXIS).smobbed_copy ();
388 }
389
390 /*
391  * Used by Slur_engraver:: and Phrasing_slur_engraver::
392  */
393 void
394 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
395                                           vector<Grob *> &slurs,
396                                           vector<Grob *> &end_slurs)
397 {
398   if (slurs.empty () && end_slurs.empty ())
399     return;
400
401   Grob *e = info.grob ();
402   SCM avoid = e->get_property ("avoid-slur");
403   Grob *slur;
404   if (end_slurs.size () && !slurs.size ())
405     slur = end_slurs[0];
406   else
407     slur = slurs[0];
408
409   if (Tie::has_interface (e)
410       || avoid == ly_symbol2scm ("inside"))
411     {
412       for (vsize i = slurs.size (); i--;)
413         add_extra_encompass (slurs[i], e);
414       for (vsize i = end_slurs.size (); i--;)
415         add_extra_encompass (end_slurs[i], e);
416       if (slur)
417         e->set_object ("slur", slur->self_scm ());
418     }
419   else if (avoid == ly_symbol2scm ("outside")
420            || avoid == ly_symbol2scm ("around"))
421     {
422       if (slur)
423         {
424           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
425           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
426           e->set_object ("slur", slur->self_scm ());
427         }
428     }
429   else if (avoid != ly_symbol2scm ("ignore"))
430     e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
431                     e->name ().c_str ()));
432 }
433
434 /*
435   A callback that will be chained together with the original cross-staff
436   value of a grob that is placed 'outside or 'around a slur. This just says
437   that any grob becomes cross-staff if it is placed 'outside or 'around a
438   cross-staff slur.
439 */
440 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
441 SCM
442 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
443 {
444   if (previous == SCM_BOOL_T)
445     return previous;
446
447   Grob *me = unsmob_grob (smob);
448   Grob *slur = unsmob_grob (me->get_object ("slur"));
449
450   if (!slur)
451     return SCM_BOOL_F;
452   return slur->get_property ("cross-staff");
453 }
454
455 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
456 SCM
457 Slur::calc_cross_staff (SCM smob)
458 {
459   Grob *me = unsmob_grob (smob);
460
461   extract_grob_set (me, "note-columns", cols);
462   extract_grob_set (me, "encompass-objects", extras);
463
464   for (vsize i = 0; i < cols.size (); i++)
465     {
466       if (Grob *s = Note_column::get_stem (cols[i]))
467         if (to_boolean (s->get_property ("cross-staff")))
468           return SCM_BOOL_T;
469     }
470
471   /* the separation items are dealt with in replace_breakable_encompass_objects
472      so we can ignore them here */
473   vector<Grob *> non_sep_extras;
474   for (vsize i = 0; i < extras.size (); i++)
475     if (!Separation_item::has_interface (extras[i]))
476       non_sep_extras.push_back (extras[i]);
477
478   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
479   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
480
481   return scm_from_bool (common != me->get_parent (Y_AXIS));
482 }
483
484 ADD_INTERFACE (Slur,
485                "A slur."
486                "\n"
487                "The following properties may be set in the @code{details}"
488                " list.\n"
489                "\n"
490                "@table @code\n"
491                "@item region-size\n"
492                "Size of region (in staff spaces) for determining"
493                " potential endpoints in the Y direction.\n"
494                "@item head-encompass-penalty\n"
495                "Demerit to apply when note heads collide with a slur.\n"
496                "@item stem-encompass-penalty\n"
497                "Demerit to apply when stems collide with a slur.\n"
498                "@item edge-attraction-factor\n"
499                "Factor used to calculate the demerit for distances"
500                " between slur endpoints and their corresponding base"
501                " attachments.\n"
502                "@item same-slope-penalty\n"
503                "Demerit for slurs with attachment points that are"
504                " horizontally aligned.\n"
505                "@item steeper-slope-factor\n"
506                "Factor used to calculate demerit only if this slur is"
507                " not broken.\n"
508                "@item non-horizontal-penalty\n"
509                "Demerit for slurs with attachment points that are not"
510                " horizontally aligned.\n"
511                "@item max-slope\n"
512                "The maximum slope allowed for this slur.\n"
513                "@item max-slope-factor\n"
514                "Factor that calculates demerit based on the max slope.\n"
515                "@item free-head-distance\n"
516                "The amount of vertical free space that must exist"
517                " between a slur and note heads.\n"
518                "@item absolute-closeness-measure\n"
519                "Factor to calculate demerit for variance between a note"
520                " head and slur.\n"
521                "@item extra-object-collision-penalty\n"
522                "Factor to calculate demerit for extra objects that the"
523                " slur encompasses, including accidentals, fingerings, and"
524                " tuplet numbers.\n"
525                "@item accidental-collision\n"
526                "Factor to calculate demerit for @code{Accidental} objects"
527                " that the slur encompasses.  This property value replaces"
528                " the value of @code{extra-object-collision-penalty}.\n"
529                "@item extra-encompass-free-distance\n"
530                "The amount of vertical free space that must exist"
531                " between a slur and various objects it encompasses,"
532                " including accidentals, fingerings, and tuplet numbers.\n"
533                "@item extra-encompass-collision-distance\n"
534                "This detail is currently unused.\n"
535                "@item head-slur-distance-factor\n"
536                "Factor to calculate demerit for variance between a note"
537                " head and slur.\n"
538                "@item head-slur-distance-max-ratio\n"
539                "The maximum value for the ratio of distance between a"
540                " note head and slur.\n"
541                "@item free-slur-distance\n"
542                "The amount of vertical free space that must exist"
543                " between adjacent slurs.  This subproperty only works"
544                " for @code{PhrasingSlur}.\n"
545                "@item edge-slope-exponent\n"
546                "Factor used to calculate the demerit for the slope of"
547                " a slur near its endpoints; a larger value yields a"
548                " larger demerit.\n"
549                "@end table\n",
550
551                /* properties */
552                "annotation "
553                "avoid-slur "  /* UGH. */
554                "control-points "
555                "dash-definition "
556                "details "
557                "direction "
558                "eccentricity "
559                "encompass-objects "
560                "height-limit "
561                "inspect-quants "
562                "inspect-index "
563                "line-thickness "
564                "note-columns "
565                "skyline-quantizing "
566                "positions "
567                "ratio "
568                "thickness "
569                "vertical-skylines "
570               );
571