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