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