]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
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   SCM encompass_scm = me->get_object ("encompass-objects");
226   if (unsmob<Grob_array> (encompass_scm))
227     {
228       vector<Grob *> &arr
229         = unsmob<Grob_array> (encompass_scm)->array_reference ();
230       arr = new_encompasses;
231     }
232 }
233
234 Bezier
235 Slur::get_curve (Grob *me)
236 {
237   Bezier b;
238   int i = 0;
239   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
240        s = scm_cdr (s))
241     b.control_[i++] = ly_scm2offset (scm_car (s));
242
243   return b;
244 }
245
246 void
247 Slur::add_column (Grob *me, Grob *n)
248 {
249   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
250   add_bound_item (dynamic_cast<Spanner *> (me), n);
251 }
252
253 void
254 Slur::add_extra_encompass (Grob *me, Grob *n)
255 {
256   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
257 }
258
259 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
260 SCM
261 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
262 {
263   int start = robust_scm2int (start_scm, 0);
264   int end = robust_scm2int (end_scm, 0);
265   Grob *script = unsmob<Grob> (grob);
266   Grob *slur = unsmob<Grob> (script->get_object ("slur"));
267   if (!slur)
268     return offset_scm;
269
270   SCM avoid = script->get_property ("avoid-slur");
271   if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
272       && !scm_is_eq (avoid, ly_symbol2scm ("around")))
273     return offset_scm;
274
275   Real offset = robust_scm2double (offset_scm, 0.0);
276   Direction dir = get_grob_direction (script);
277   return scm_from_double (offset + dir * slur->pure_y_extent (slur, start, end).length () / 4);
278 }
279
280 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
281 SCM
282 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
283 {
284   Grob *script = unsmob<Grob> (grob);
285   Grob *slur = unsmob<Grob> (script->get_object ("slur"));
286
287   if (!slur)
288     return offset_scm;
289
290   SCM avoid = script->get_property ("avoid-slur");
291   if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
292       && !scm_is_eq (avoid, ly_symbol2scm ("around")))
293     return offset_scm;
294
295   Direction dir = get_grob_direction (script);
296   if (dir == CENTER)
297     return offset_scm;
298
299   Grob *cx = script->common_refpoint (slur, X_AXIS);
300   Grob *cy = script->common_refpoint (slur, Y_AXIS);
301
302   Bezier curve = Slur::get_curve (slur);
303
304   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
305                            slur->relative_coordinate (cy, Y_AXIS)));
306
307   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
308   Interval xext = robust_relative_extent (script, cx, X_AXIS);
309   Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
310
311   /*
312     cannot use is_empty because some 0-extent scripts
313     come up with TabStaffs.
314   */
315   if (xext.length () <= 0 || yext.length () <= 0)
316     return offset_scm;
317
318   bool contains = false;
319   for (LEFT_and_RIGHT (d))
320     contains |= slur_wid.contains (xext[d]);
321
322   if (!contains)
323     return offset_scm;
324
325   Real offset = robust_scm2double (offset_scm, 0);
326   yext.translate (offset);
327
328   /* FIXME: slur property, script property?  */
329   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
330                                          0.0);
331   yext.widen (slur_padding);
332
333   Interval exts[] = {xext, yext};
334   bool do_shift = false;
335   Real EPS = 1.0e-5;
336   if (scm_is_eq (avoid, ly_symbol2scm ("outside")))
337     {
338       for (LEFT_and_RIGHT (d))
339         {
340           Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
341           Real y = curve.get_other_coordinate (X_AXIS, x);
342           do_shift = y == minmax (dir, yext[-dir], y);
343           if (do_shift)
344             break;
345         }
346     }
347   else
348     {
349       for (int a = X_AXIS; a < NO_AXES; a++)
350         {
351           for (LEFT_and_RIGHT (d))
352             {
353               vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
354               for (vsize i = 0; i < coords.size (); i++)
355                 {
356                   do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
357                   if (do_shift)
358                     break;
359                 }
360               if (do_shift)
361                 break;
362             }
363           if (do_shift)
364             break;
365         }
366     }
367
368   Real avoidance_offset = do_shift ? curve.minmax (X_AXIS, std::max (xext[LEFT], curve.control_[0][X_AXIS] + EPS), std::min (xext[RIGHT], curve.control_[3][X_AXIS] - EPS), dir) - yext[-dir] : 0.0;
369
370   return scm_from_double (offset + avoidance_offset);
371 }
372
373 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
374 SCM
375 Slur::vertical_skylines (SCM smob)
376 {
377   Grob *me = unsmob<Grob> (smob);
378   vector<Box> boxes;
379
380   if (!me)
381     return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
382
383   Bezier curve = Slur::get_curve (me);
384   vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
385   for (vsize i = 0; i < box_count; i++)
386     {
387       Box b;
388       b.add_point (curve.curve_point (i * 1.0 / box_count));
389       b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
390       boxes.push_back (b);
391     }
392
393   return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
394 }
395
396 /*
397  * Used by Slur_engraver:: and Phrasing_slur_engraver::
398  */
399 void
400 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
401                                           vector<Grob *> &slurs,
402                                           vector<Grob *> &end_slurs)
403 {
404   if (slurs.empty () && end_slurs.empty ())
405     return;
406
407   Grob *e = info.grob ();
408   SCM avoid = e->get_property ("avoid-slur");
409   Grob *slur;
410   if (end_slurs.size () && !slurs.size ())
411     slur = end_slurs[0];
412   else
413     slur = slurs[0];
414
415   if (has_interface<Tie> (e)
416       || scm_is_eq (avoid, ly_symbol2scm ("inside")))
417     {
418       for (vsize i = slurs.size (); i--;)
419         add_extra_encompass (slurs[i], e);
420       for (vsize i = end_slurs.size (); i--;)
421         add_extra_encompass (end_slurs[i], e);
422       if (slur)
423         e->set_object ("slur", slur->self_scm ());
424     }
425   else if (scm_is_eq (avoid, ly_symbol2scm ("outside"))
426            || scm_is_eq (avoid, ly_symbol2scm ("around")))
427     {
428       if (slur)
429         {
430           chain_offset_callback (e,
431                                  Unpure_pure_container::make_smob (outside_slur_callback_proc,
432                                                                    pure_outside_slur_callback_proc),
433                                  Y_AXIS);
434           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
435           e->set_object ("slur", slur->self_scm ());
436         }
437     }
438   else if (!scm_is_eq (avoid, ly_symbol2scm ("ignore")))
439     e->warning (_f ("Ignoring grob for slur: %s.  avoid-slur not set?",
440                     e->name ().c_str ()));
441 }
442
443 /*
444   A callback that will be chained together with the original cross-staff
445   value of a grob that is placed 'outside or 'around a slur. This just says
446   that any grob becomes cross-staff if it is placed 'outside or 'around a
447   cross-staff slur.
448 */
449 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
450 SCM
451 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
452 {
453   if (to_boolean (previous))
454     return previous;
455
456   Grob *me = unsmob<Grob> (smob);
457   Grob *slur = unsmob<Grob> (me->get_object ("slur"));
458
459   if (!slur)
460     return SCM_BOOL_F;
461   return slur->get_property ("cross-staff");
462 }
463
464 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
465 SCM
466 Slur::calc_cross_staff (SCM smob)
467 {
468   Grob *me = unsmob<Grob> (smob);
469
470   extract_grob_set (me, "note-columns", cols);
471   extract_grob_set (me, "encompass-objects", extras);
472
473   for (vsize i = 0; i < cols.size (); i++)
474     {
475       if (Grob *s = Note_column::get_stem (cols[i]))
476         if (to_boolean (s->get_property ("cross-staff")))
477           return SCM_BOOL_T;
478     }
479
480   /* the separation items are dealt with in replace_breakable_encompass_objects
481      so we can ignore them here */
482   vector<Grob *> non_sep_extras;
483   for (vsize i = 0; i < extras.size (); i++)
484     if (!has_interface<Separation_item> (extras[i]))
485       non_sep_extras.push_back (extras[i]);
486
487   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
488   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
489
490   return scm_from_bool (common != me->get_parent (Y_AXIS));
491 }
492
493 ADD_INTERFACE (Slur,
494                "A slur."
495                "\n"
496                "The following properties may be set in the @code{details}"
497                " list.\n"
498                "\n"
499                "@table @code\n"
500                "@item region-size\n"
501                "Size of region (in staff spaces) for determining"
502                " potential endpoints in the Y direction.\n"
503                "@item head-encompass-penalty\n"
504                "Demerit to apply when note heads collide with a slur.\n"
505                "@item stem-encompass-penalty\n"
506                "Demerit to apply when stems collide with a slur.\n"
507                "@item edge-attraction-factor\n"
508                "Factor used to calculate the demerit for distances"
509                " between slur endpoints and their corresponding base"
510                " attachments.\n"
511                "@item same-slope-penalty\n"
512                "Demerit for slurs with attachment points that are"
513                " horizontally aligned.\n"
514                "@item steeper-slope-factor\n"
515                "Factor used to calculate demerit only if this slur is"
516                " not broken.\n"
517                "@item non-horizontal-penalty\n"
518                "Demerit for slurs with attachment points that are not"
519                " horizontally aligned.\n"
520                "@item max-slope\n"
521                "The maximum slope allowed for this slur.\n"
522                "@item max-slope-factor\n"
523                "Factor that calculates demerit based on the max slope.\n"
524                "@item free-head-distance\n"
525                "The amount of vertical free space that must exist"
526                " between a slur and note heads.\n"
527                "@item absolute-closeness-measure\n"
528                "Factor to calculate demerit for variance between a note"
529                " head and slur.\n"
530                "@item extra-object-collision-penalty\n"
531                "Factor to calculate demerit for extra objects that the"
532                " slur encompasses, including accidentals, fingerings, and"
533                " tuplet numbers.\n"
534                "@item accidental-collision\n"
535                "Factor to calculate demerit for @code{Accidental} objects"
536                " that the slur encompasses.  This property value replaces"
537                " the value of @code{extra-object-collision-penalty}.\n"
538                "@item extra-encompass-free-distance\n"
539                "The amount of vertical free space that must exist"
540                " between a slur and various objects it encompasses,"
541                " including accidentals, fingerings, and tuplet numbers.\n"
542                "@item extra-encompass-collision-distance\n"
543                "This detail is currently unused.\n"
544                "@item head-slur-distance-factor\n"
545                "Factor to calculate demerit for variance between a note"
546                " head and slur.\n"
547                "@item head-slur-distance-max-ratio\n"
548                "The maximum value for the ratio of distance between a"
549                " note head and slur.\n"
550                "@item gap-to-staffline-inside\n"
551                "Minimum gap inside the curve of the slur"
552                " where the slur is parallel to a staffline.\n"
553                "@item gap-to-staffline-outside\n"
554                "Minimum gap outside the curve of the slur"
555                " where the slur is parallel to a staffline.\n"
556                "@item free-slur-distance\n"
557                "The amount of vertical free space that must exist"
558                " between adjacent slurs.  This subproperty only works"
559                " for @code{PhrasingSlur}.\n"
560                "@item edge-slope-exponent\n"
561                "Factor used to calculate the demerit for the slope of"
562                " a slur near its endpoints; a larger value yields a"
563                " larger demerit.\n"
564                "@end table\n",
565
566                /* properties */
567                "annotation "
568                "avoid-slur "  /* UGH. */
569                "control-points "
570                "dash-definition "
571                "details "
572                "direction "
573                "eccentricity "
574                "encompass-objects "
575                "height-limit "
576                "inspect-quants "
577                "inspect-index "
578                "line-thickness "
579                "note-columns "
580                "positions "
581                "ratio "
582                "thickness "
583               );
584