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