]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur-quanting.cc
* lily/slur-quanting.cc (score_extra_encompass): Bigger penalty
[lilypond.git] / lily / slur-quanting.cc
1 /*
2   slur.cc -- implement score based Slur
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8 */
9
10 #include <math.h>
11
12 #include "accidental-interface.hh"
13 #include "beam.hh"
14 #include "directional-element-interface.hh"
15 #include "group-interface.hh"
16 #include "lily-guile.hh"
17 #include "new-slur.hh"
18 #include "note-column.hh"
19 #include "output-def.hh"
20 #include "pitch.hh"
21 #include "slur-bezier-bow.hh"
22 #include "spanner.hh"
23 #include "staff-symbol-referencer.hh"
24 #include "staff-symbol.hh"
25 #include "stem.hh"
26 #include "warn.hh"
27
28 struct Slur_score
29 {
30   Drul_array<Offset> attachment_;
31   Real score_;
32   Bezier curve_;
33
34 #if DEBUG_SLUR_QUANTING
35   String score_card_;
36 #endif
37
38   Slur_score()
39   {
40     score_ = 0.0;
41   }
42 };
43
44 /*
45   TODO: put in details property.,
46
47   use lowercase.
48 */
49 struct Slur_score_parameters
50 {
51   int SLUR_REGION_SIZE;
52   Real HEAD_ENCOMPASS_PENALTY;
53   Real STEM_ENCOMPASS_PENALTY;
54   Real CLOSENESS_FACTOR;
55   Real EDGE_ATTRACTION_FACTOR;
56   Real SAME_SLOPE_PENALTY;
57   Real STEEPER_SLOPE_FACTOR;
58   Real NON_HORIZONTAL_PENALTY;
59   Real HEAD_STRICT_FREE_SPACE;
60   Real MAX_SLOPE;
61   Real MAX_SLOPE_FACTOR;
62   Real EXTRA_OBJECT_COLLISION;
63   Real ACCIDENTAL_COLLISION;
64   Real FREE_HEAD_DISTANCE;
65   Slur_score_parameters ();
66 };
67
68 /*
69   TODO:
70
71   - curve around flag for y coordinate
72   - better scoring.
73   - short-cut: try a smaller region first.
74   - collisions with accidentals
75   - collisions with articulations (staccato, portato, sforzato, ...)
76   -
77 */
78 struct Encompass_info
79 {
80   Real x_;
81   Real stem_;
82   Real head_;
83   Encompass_info ()
84   {
85     x_ = 0.0;
86     stem_ = 0.0;
87     head_ = 0.0;
88   }
89 };
90
91 struct Bound_info
92 {
93   Box stem_extent_;
94   Direction stem_dir_;
95   Grob *bound_;
96   Grob *note_column_;
97   Grob *slur_head_;
98   Grob *staff_;
99   Grob *stem_;
100   Interval slur_head_extent_;
101   Real neighbor_y_;
102   Real staff_space_;
103
104   Bound_info ()
105   {
106     stem_ = 0;
107     neighbor_y_ = 0;
108     staff_ = 0;
109     slur_head_ = 0;
110     stem_dir_ = CENTER;
111     note_column_ = 0;
112   }
113 };
114
115
116
117
118 static void
119 score_extra_encompass (Grob *me, Grob *common[],
120                        Slur_score_parameters *score_param,
121                        Drul_array<Bound_info> ,
122                        Drul_array<Offset> ,
123                        Array<Slur_score> * scores);
124 static void score_slopes (Grob *me, Grob *common[],
125                           Slur_score_parameters *score_param,
126                           Drul_array<Bound_info>,
127                           Drul_array<Offset> base_attach,
128                           Array<Slur_score> *scores);
129
130 static void score_edges (Grob *me, Grob *common[],
131                          Slur_score_parameters *score_param,
132                          Drul_array<Bound_info> extremes,
133                          Drul_array<Offset> base_attach,
134                          Array<Slur_score> *scores);
135 static void score_encompass (Grob *me, Grob *common[],
136                              Slur_score_parameters*,
137                              Drul_array<Bound_info>,
138                              Drul_array<Offset>, Array<Slur_score> *scores);
139 static Bezier avoid_staff_line (Grob *me, Grob **common,
140                                 Drul_array<Bound_info> extremes,
141                                 Bezier bez);
142
143 static Encompass_info get_encompass_info (Grob *me,
144                                           Grob *col,
145                                           Grob **common);
146 static Bezier get_bezier (Grob *me, Drul_array<Offset>, Real, Real);
147 static Direction get_default_dir (Grob *me);
148
149 static void set_end_points (Grob *);
150 static Real broken_trend_y (Grob *me, Grob **, Direction dir);
151 static Drul_array<Bound_info> get_bound_info (Spanner *me, Grob **common);
152
153 static void generate_curves (Grob *me,
154                              Grob *common[],
155                              Drul_array<Bound_info> extremes,
156                              Drul_array<Offset> base_attach,
157                              Array<Slur_score> *scores);
158 static Array<Slur_score> enumerate_attachments
159 (Grob *me, Grob *common[], Slur_score_parameters *score_param,
160  Drul_array<Bound_info> extremes,
161  Drul_array<Offset> base_attachment, Drul_array<Real> end_ys);
162 static Drul_array<Offset> get_base_attachments
163 (Spanner *sp, Grob **common, Drul_array<Bound_info> extremes);
164 static Drul_array<Real> get_y_attachment_range
165 (Spanner *sp, Grob **common, Drul_array<Bound_info> extremes,
166  Drul_array<Offset> base_attachment);
167
168 void
169 init_score_param (Slur_score_parameters *score_param)
170 {
171   score_param->SLUR_REGION_SIZE = 5;
172   score_param->HEAD_ENCOMPASS_PENALTY = 1000.0;
173   score_param->STEM_ENCOMPASS_PENALTY = 30.0;
174   score_param->CLOSENESS_FACTOR = 10;
175   score_param->EDGE_ATTRACTION_FACTOR = 4;
176   score_param->SAME_SLOPE_PENALTY = 20;
177   score_param->STEEPER_SLOPE_FACTOR = 50;
178   score_param->NON_HORIZONTAL_PENALTY = 15;
179   score_param->HEAD_STRICT_FREE_SPACE = 0.2;
180   score_param->MAX_SLOPE = 1.1;
181   score_param->MAX_SLOPE_FACTOR = 10;
182   score_param->FREE_HEAD_DISTANCE = 0.3;
183   score_param->EXTRA_OBJECT_COLLISION = 50;
184   score_param->ACCIDENTAL_COLLISION = 3;
185 }
186
187 Slur_score_parameters::Slur_score_parameters()
188 {
189   init_score_param (this);
190 }
191
192 /* HDIR indicates the direction for the slur.  */
193 Real
194 broken_trend_y (Grob *me, Grob **common, Direction hdir)
195 {
196   /* A broken slur should maintain the same vertical trend
197      the unbroken slur would have had.  */
198   Real by = 0.0;
199   if (Spanner *mother = dynamic_cast<Spanner*> (me->original_))
200     {
201       int k = broken_spanner_index (dynamic_cast<Spanner*> (me));
202       int j = k + hdir;
203       if (j < 0 || j >= mother->broken_intos_.size ())
204         return by;
205
206       Grob *neighbor = mother->broken_intos_[j];
207       if (hdir == RIGHT)
208         neighbor->set_property ("direction",
209                                 me->get_property ("direction"));
210
211       Spanner *common_mother
212         = dynamic_cast<Spanner*> (common[Y_AXIS]->original_);
213       int common_k
214         = broken_spanner_index (dynamic_cast<Spanner*> (common[Y_AXIS]));
215       int common_j = common_k + hdir;
216
217       if (common_j < 0 || common_j >= common_mother->broken_intos_.size())
218         return by;
219
220       Grob *common_next_system = common_mother->broken_intos_[common_j];
221       Link_array<Grob> neighbor_cols
222         = Pointer_group_interface__extract_grobs (neighbor, (Grob *)0,
223                                                   "note-columns");
224
225       Grob *neighbor_col
226         = (hdir == RIGHT) ? neighbor_cols[0] : neighbor_cols.top ();
227       Grob *neighbor_common
228         = common_next_system->common_refpoint (neighbor_col, Y_AXIS);
229
230       Direction vdir = get_grob_direction (me);
231       Real neighbor_y
232         = neighbor_col->extent (neighbor_common, Y_AXIS)
233         .linear_combination (int(neighbor_cols.size()==1 ? CENTER : vdir))
234         - common_next_system->relative_coordinate (neighbor_common, Y_AXIS);
235
236       Link_array<Grob> my_cols
237         = Pointer_group_interface__extract_grobs (me, (Grob *)0,
238                                                   "note-columns");
239
240       Grob *extreme_col = (hdir == RIGHT) ? my_cols.top() : my_cols[0];
241       Real y = extreme_col->extent (common[Y_AXIS], Y_AXIS)
242         .linear_combination (int ((my_cols.size() == 1) ? CENTER : vdir));
243       by = (y*neighbor_cols.size() + neighbor_y*my_cols.size()) /
244         (my_cols.size() + neighbor_cols.size());
245     }
246   return by;
247 }
248
249 Encompass_info
250 get_encompass_info (Grob *me,
251                     Grob *col,
252                     Grob **common)
253 {
254   Grob *stem = unsmob_grob (col->get_property ("stem"));
255   Encompass_info ei;
256   Direction dir = get_grob_direction (me);
257
258   if (!stem)
259     {
260       programming_error ("No stem for note column?");
261       ei.x_ = col->relative_coordinate (common[X_AXIS], X_AXIS);
262       ei.head_ = ei.stem_ = col->extent (common[Y_AXIS],
263                                          Y_AXIS)[get_grob_direction (me)];
264       return ei;
265     }
266   Direction stem_dir = get_grob_direction (stem);
267
268   if (Grob *head = Note_column::first_head (col))
269     ei.x_ = head->extent (common[X_AXIS], X_AXIS).center ();
270   else
271     ei.x_ = col->extent (common[X_AXIS], X_AXIS).center ();
272
273   Grob *h = Stem::extremal_heads (stem)[Direction (dir)];
274   if (!h)
275     {
276       ei.head_ = ei.stem_ = col->extent (common[Y_AXIS], Y_AXIS)[dir];
277       return ei;
278     }
279
280   ei.head_ = h->extent (common[Y_AXIS], Y_AXIS)[dir];
281
282   if ((stem_dir == dir)
283       && !stem->extent (stem, Y_AXIS).is_empty ())
284     {
285       ei.stem_ = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
286       if (Grob *b = Stem::get_beam (stem))
287         ei.stem_ += stem_dir * 0.5 * Beam::get_thickness (b);
288       ei.x_ = stem->extent (common[X_AXIS], X_AXIS).center ();
289     }
290   else
291     ei.stem_ = ei.head_;
292
293   return ei;
294 }
295
296
297 Direction
298 get_default_dir (Grob*me)
299 {
300   Link_array<Grob> encompasses
301     = Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-columns");
302
303   Direction d = DOWN;
304   for (int i= 0; i < encompasses.size (); i ++)
305     {
306       if (Note_column::dir (encompasses[i]) < 0)
307         {
308           d = UP;
309           break;
310         }
311     }
312   return d;
313 }
314
315 MAKE_SCHEME_CALLBACK (New_slur, after_line_breaking,1);
316 SCM
317 New_slur::after_line_breaking (SCM smob)
318 {
319   Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
320   if (!scm_ilength (me->get_property ("note-columns")))
321     {
322       me->suicide ();
323       return SCM_UNSPECIFIED;
324     }
325
326   if (!get_grob_direction (me))
327     set_grob_direction (me, get_default_dir (me));
328
329   set_end_points (me);
330
331   return SCM_UNSPECIFIED;
332 }
333
334 Bezier
335 get_bezier (Grob *me, Drul_array<Offset> extremes, Real r_0, Real h_inf)
336 {
337   Array<Offset> encompasses;
338   encompasses.push (extremes[LEFT]);
339   encompasses.push (extremes[RIGHT]);
340
341   Slur_bezier_bow bb (encompasses,
342                       get_grob_direction (me), h_inf, r_0);
343
344   return bb.get_bezier ();
345 }
346
347 Drul_array<Bound_info>
348 get_bound_info (Spanner* me, Grob **common)
349 {
350   Drul_array<Bound_info> extremes;
351   Direction d = LEFT;
352   Direction dir = get_grob_direction (me);
353
354   do
355     {
356       extremes[d].bound_ = me->get_bound (d);
357       if (Note_column::has_interface (extremes[d].bound_))
358         {
359           extremes[d].note_column_ = extremes[d].bound_;
360           extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_);
361           extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_);
362           extremes[d].stem_extent_[X_AXIS]
363             = extremes[d].stem_->extent (common[X_AXIS], X_AXIS);
364           extremes[d].stem_extent_[Y_AXIS]
365             = extremes[d].stem_->extent (common[Y_AXIS], Y_AXIS);
366           extremes[d].slur_head_
367             = Stem::extremal_heads (extremes[d].stem_)[dir];
368           extremes[d].slur_head_extent_
369             = extremes[d].slur_head_->extent (common[X_AXIS], X_AXIS);
370           extremes[d].staff_ = Staff_symbol_referencer
371             ::get_staff_symbol (extremes[d].slur_head_);
372           extremes[d].staff_space_ = Staff_symbol_referencer
373             ::staff_space (extremes[d].slur_head_);
374         }
375       else
376         extremes[d].neighbor_y_ = broken_trend_y (me, common, d);
377     }
378   while (flip (&d) != LEFT);
379   return extremes;
380 }
381
382 void
383 set_end_points (Grob *me)
384 {
385   Link_array<Grob> columns
386     = Pointer_group_interface__extract_grobs (me, (Grob *)0, "note-columns");
387   Slur_score_parameters params;
388   if (columns.is_empty ())
389     {
390       me->suicide ();
391       return;
392     }
393
394   SCM eltlist = me->get_property ("note-columns");
395   SCM extra_list = me->get_property ("encompass-objects");
396   Spanner *sp = dynamic_cast<Spanner*> (me);
397
398   Grob *common[] = {0, 0};
399   for (int i = X_AXIS; i < NO_AXES; i++)
400     {
401       Axis a = (Axis)i;
402       common[a] = common_refpoint_of_list (eltlist, me, a);
403       common[a] = common_refpoint_of_list (extra_list, common[a], a);
404     }
405
406   common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (RIGHT),
407                                                     X_AXIS);
408   common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (LEFT),
409                                                     X_AXIS);
410
411   Drul_array<Bound_info> extremes = get_bound_info (sp, common);
412   Drul_array<Offset> base_attachment
413     = get_base_attachments (sp, common, extremes);
414   Drul_array<Real> end_ys
415     = get_y_attachment_range (sp, common, extremes, base_attachment);
416   Array<Slur_score> scores = enumerate_attachments (me, common, &params,
417                                                     extremes, base_attachment,
418                                                     end_ys);
419
420   generate_curves (me, common, extremes, base_attachment, &scores);
421   score_edges (me, common, &params,extremes, base_attachment, &scores);
422   score_slopes (me, common, &params,extremes, base_attachment, &scores);
423   score_encompass (me, common, &params,extremes, base_attachment, &scores);
424   score_extra_encompass (me, common, &params,extremes, base_attachment,
425                          &scores);
426
427   Real opt = 1e6;
428   int opt_idx = 0;
429   // why backwards?
430   for (int i = scores.size (); i--;)
431     {
432       if (scores[i].score_ < opt)
433         {
434           opt = scores[i].score_;
435           opt_idx = i;
436         }
437     }
438
439 #if DEBUG_SLUR_QUANTING
440   SCM inspect_quants = me->get_property ("inspect-quants");
441   if (to_boolean (me->get_paper ()
442                   ->lookup_variable (ly_symbol2scm ("debug-slur-quanting")))
443       && ly_c_pair_p (inspect_quants))
444     {
445       Drul_array<Real> ins = ly_scm2interval (inspect_quants);
446       int i = 0;
447
448       Real mindist = 1e6;
449       for (; i < scores.size (); i ++)
450         {
451           Real d =fabs (scores[i].attachment_[LEFT][Y_AXIS] - ins[LEFT])
452             + fabs (scores[i].attachment_[RIGHT][Y_AXIS] - ins[RIGHT]);
453           if (d < mindist)
454             {
455               opt_idx = i;
456               mindist= d;
457             }
458         }
459       if (mindist > 1e5)
460         programming_error ("Could not find quant.");
461     }
462   scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
463
464   // debug quanting
465   me->set_property ("quant-score",
466                     scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
467 #endif
468
469   Bezier b = scores[opt_idx].curve_;
470   SCM controls = SCM_EOL;
471   for (int i = 4; i--;)
472     {
473       Offset o = b.control_[i] -
474         Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
475                 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
476
477       controls = scm_cons (ly_offset2scm (o), controls);
478     }
479
480   me->set_property ("control-points", controls);
481 }
482
483
484 Drul_array<Real>
485 get_y_attachment_range (Spanner*me,
486                         Grob **common,
487                         Drul_array<Bound_info> extremes,
488                         Drul_array<Offset> base_attachment)
489 {
490   Drul_array<Real> end_ys;
491   Direction dir = get_grob_direction (me);
492   Direction d = LEFT;
493   do
494     {
495       if (extremes[d].note_column_)
496         {
497           end_ys[d] = dir
498             * ((dir * (base_attachment[d][Y_AXIS] + 4.0 * dir))
499                >? (dir * (dir + extremes[d].note_column_->extent(common[Y_AXIS],
500                                                                  Y_AXIS)[dir]))
501                >? (dir * base_attachment[-d][Y_AXIS]));
502         }
503       else
504         end_ys[d] = extremes[d].neighbor_y_ + 4.0 * dir;
505     }
506   while (flip (&d) != LEFT);
507
508   return end_ys;
509 }
510
511 Drul_array<Offset>
512 get_base_attachments (Spanner *me,
513                       Grob **common, Drul_array<Bound_info> extremes)
514 {
515   Link_array<Grob> columns
516     = Pointer_group_interface__extract_grobs (me, (Grob *)0, "note-columns");
517   Drul_array<Offset> base_attachment;
518   Slur_score_parameters params;
519   Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
520   Direction dir = get_grob_direction (me);
521   Direction d = LEFT;
522   do
523     {
524       Grob *stem = extremes[d].stem_;
525       Grob *head = extremes[d].slur_head_;
526
527       Real x, y;
528       if (!extremes[d].note_column_)
529         {
530           y = extremes[d].neighbor_y_;
531           if (d== RIGHT)
532             x = extremes[d].bound_->extent (common[X_AXIS], X_AXIS)[d];
533           else
534             x = me->get_broken_left_end_align ();
535         }
536       else
537         {
538           if (stem
539               && extremes[d].stem_dir_ == dir
540               && Stem::get_beaming (stem, -d)
541               && columns.size () > 2
542               )
543             y = extremes[d].stem_extent_[Y_AXIS][dir];
544           else if (head)
545             y = head->extent (common[Y_AXIS], Y_AXIS)[dir];
546           y += dir * 0.5 * staff_space;
547
548           if (stem)
549             {
550               SCM scripts = stem->get_property ((dir == UP
551                                                  ? "script-up"
552                                                  : "script-down"));
553
554               /* FIXME: number of scripts, height, priority?
555                  articulation scripts should be inside slur, other,
556                  such as dynamics, pedals, fingering, should be
557                  outside.  */
558               if (!SCM_NULLP (scripts))
559                 y += dir * staff_space;
560             }
561
562           Real pos
563             = (y - extremes[d].staff_->relative_coordinate (common[Y_AXIS],
564                                                             Y_AXIS))
565             * 2.0 / Staff_symbol::staff_space (extremes[d].staff_);
566
567           /* start off staffline. */
568           if (fabs (pos - round (pos)) < 0.2
569               && Staff_symbol_referencer::on_staffline (head, (int) rint (pos))
570               && Staff_symbol_referencer::line_count (head) - 1 >= rint (pos)
571               )
572             // TODO: calc from slur thick & line thick, parameter.      
573             y += 1.5 * staff_space * dir / 10;
574
575           Grob * fh = Note_column::first_head (extremes[d].note_column_);
576           x = fh->extent (common[X_AXIS], X_AXIS).linear_combination (CENTER);
577         }
578       base_attachment[d] = Offset (x, y);
579
580     } while (flip (&d) != LEFT);
581
582   return base_attachment;
583 }
584
585 void
586 generate_curves (Grob *me, Grob **common,
587                  Drul_array<Bound_info> extremes,
588                  Drul_array<Offset>,
589                  Array<Slur_score> *scores)
590 {
591   (void) common;
592   (void) extremes;
593   Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
594   Real r_0 = robust_scm2double (me->get_property ("ratio"), 0.33);
595   Real h_inf = staff_space * ly_scm2double (me->get_property ("height-limit"));
596   for (int i = 0; i < scores->size(); i++)
597     {
598       Bezier bez= get_bezier (me, (*scores)[i].attachment_, r_0, h_inf);
599       bez = avoid_staff_line (me, common, extremes, bez);
600       (*scores)[i].attachment_[LEFT] = bez.control_[0];
601       (*scores)[i].attachment_[RIGHT] = bez.control_[3];
602       (*scores)[i].curve_ = bez;
603     }
604 }
605
606 Bezier
607 avoid_staff_line (Grob *me, Grob **common,
608                   Drul_array<Bound_info> extremes,
609                   Bezier bez)
610 {
611   Offset horiz (1,0);
612   Array<Real> ts = bez.solve_derivative (horiz);
613   Real lt = me->get_paper ()->get_dimension (ly_symbol2scm ("linethickness"));
614   Real thick = robust_scm2double (me->get_property ("thickness"), 1.0) *  lt;
615
616   /* TODO: handle case of broken slur.  */
617   if (!ts.is_empty ()
618       && (extremes[LEFT].staff_ == extremes[RIGHT].staff_)
619       && extremes[LEFT].staff_ && extremes[RIGHT].staff_)
620     {
621       Real y = bez.curve_point (ts[0])[Y_AXIS];
622
623       Grob *staff = extremes[LEFT].staff_;
624
625       Real staff_space = extremes[LEFT].staff_space_;
626       Real p = 2 * (y - staff->relative_coordinate (common[Y_AXIS], Y_AXIS))
627         / staff_space;
628
629       Real distance = fabs (round (p) - p);     //  in halfspaces
630       if (distance < 4 * thick
631           && (int) fabs (round (p))
632           <= 2 * Staff_symbol_referencer::staff_radius (staff) + 0.1
633           && (int (fabs (round (p))) % 2
634               != Staff_symbol_referencer::line_count (staff) % 2))
635         {
636           Direction resolution_dir =
637             (distance ?  get_grob_direction (me) : Direction (sign (p - round(p))));
638
639           // TODO: parameter
640           Real newp = round (p) + resolution_dir
641             * 5 * thick;
642         
643           Real dy = (newp - p) * staff_space / 2.0;
644 #if 0
645           bez.translate (Offset (0, dy));
646 #else
647           bez.control_[1][Y_AXIS] += dy;
648           bez.control_[2][Y_AXIS] += dy;
649         
650 #endif
651         }
652     }
653   return bez;
654 }
655
656 Array<Slur_score>
657 enumerate_attachments (Grob *me, Grob *common[],
658                        Slur_score_parameters *score_param,
659                        Drul_array<Bound_info> extremes,
660                        Drul_array<Offset> base_attachment,
661                        Drul_array<Real> end_ys)
662 {
663   (void) common;
664   /*ugh.   */
665   Array<Slur_score> scores;
666
667   Direction dir = get_grob_direction (me);
668   Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
669
670   Drul_array<Offset> os;
671   os[LEFT] = base_attachment[LEFT];
672   Real minimum_length = staff_space
673     * robust_scm2double (me->get_property ("minimum-length"), 2.0);
674
675   for (int i = 0; dir * os[LEFT][Y_AXIS] <= dir * end_ys[LEFT]; i++)
676     {
677       os[RIGHT] = base_attachment[RIGHT];
678       for (int j = 0; dir * os[RIGHT][Y_AXIS] <= dir * end_ys[RIGHT]; j++)
679         {
680           Slur_score s;
681           Direction d = LEFT;
682           Drul_array<bool> attach_to_stem (false, false);
683           do
684             {
685               os[d][X_AXIS] = base_attachment[d][X_AXIS];
686               if (extremes[d].stem_
687                   && !Stem::is_invisible (extremes[d].stem_)
688                   && extremes[d].stem_dir_ == dir
689                   && dir == -d)
690                 {
691                   if (extremes[d].stem_extent_[Y_AXIS].contains (os[d][Y_AXIS]))
692                     {
693                       os[d][X_AXIS] =  extremes[d].slur_head_extent_[-d]
694                         - d * 0.3;
695                       attach_to_stem[d] = true;
696                     }
697                   else if (dir *extremes[d].stem_extent_[Y_AXIS][dir]
698                            < dir * os[d][Y_AXIS])
699                     os[d][X_AXIS] = extremes[d].stem_extent_[X_AXIS].center();
700                 }
701             }
702           while (flip (&d) != LEFT);
703
704           Offset dz;    
705           dz = os[RIGHT] - os[LEFT];
706           if (dz[X_AXIS] < minimum_length
707               || fabs (dz[Y_AXIS] / dz[X_AXIS]) > score_param->MAX_SLOPE
708               )
709             {
710               do
711                 {
712                   if (extremes[d].slur_head_)
713                     {
714                       os[d][X_AXIS] = extremes[d].slur_head_extent_.center ();
715                       attach_to_stem[d] = false;
716                     }
717                 }
718               while (flip (&d) != LEFT);
719             }
720
721           dz = os[RIGHT] - os[LEFT];
722           do
723             {
724               if (extremes[d].slur_head_
725                   && !attach_to_stem[d])
726                 {
727                   /* Horizontally move tilted slurs a little.  Move
728                      more for bigger tilts.
729                     
730                      TODO: parameter */
731                   os[d][X_AXIS]
732                     -= dir * extremes[d].slur_head_extent_.length ()
733                     * sin (dz.arg  ()) / 3;
734                 }
735             }
736           while (flip (&d) != LEFT);
737           
738           s.attachment_ = os;
739           scores.push (s);
740           
741           os[RIGHT][Y_AXIS] += dir * staff_space / 2;
742         }
743       
744       os[LEFT][Y_AXIS] += dir * staff_space / 2;
745     }
746   return scores;
747 }
748
749 void
750 score_encompass (Grob *me, Grob *common[],
751                  Slur_score_parameters *score_param,
752                  Drul_array<Bound_info> extremes,
753                  Drul_array<Offset> base_attach,
754                  Array<Slur_score> *scores)
755 {
756   (void) extremes;
757   (void) base_attach;
758
759   Link_array<Grob> encompasses
760     = Pointer_group_interface__extract_grobs (me, (Grob *)0, "note-columns");
761   Direction dir = get_grob_direction (me);
762
763   Array<Encompass_info> infos;
764
765   for (int i = 0; i < encompasses.size(); i++)
766     infos.push (get_encompass_info (me, encompasses[i], common));
767
768   for (int i = 0; i < scores->size (); i++)
769     {
770       Bezier const &bez (scores->elem (i).curve_);
771       Real demerit = 0.0;
772       for (int j = 0; j < infos.size(); j++)
773         {
774           Real x = infos[j].x_;
775
776           bool l_edge = j==0;
777           bool r_edge = j==infos.size()-1;
778           bool edge =  l_edge || r_edge;
779
780           if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
781                 && x > scores->elem (i).attachment_[LEFT][X_AXIS]))
782             continue;
783         
784           Real y = bez.get_other_coordinate (X_AXIS, x);
785           if (!edge)
786             {
787               Real head_dy = (y - infos[j].head_);
788               if (dir * head_dy < 0)
789                 demerit += score_param->HEAD_ENCOMPASS_PENALTY;
790               else
791                 {
792                   Real hd = (head_dy)
793                     ? (1 / fabs (head_dy) - 1 / score_param->FREE_HEAD_DISTANCE)
794                     : score_param->HEAD_ENCOMPASS_PENALTY;
795                   hd = (hd >? 0)<? score_param->HEAD_ENCOMPASS_PENALTY;
796
797                   demerit += hd;        
798                 }
799             }   
800
801           if (dir * (y - infos[j].stem_) < 0)
802             {
803               Real stem_dem =score_param->STEM_ENCOMPASS_PENALTY ;
804               if ((l_edge && dir == UP)
805                   || (r_edge && dir == DOWN))
806                 stem_dem /= 5;
807
808               demerit +=  stem_dem;
809             }
810           else if (!edge)
811             {
812               Interval ext;
813               ext.add_point (infos[j].stem_);
814               ext.add_point (infos[j].head_);
815
816               // ?
817               demerit += -score_param->CLOSENESS_FACTOR
818                 * (dir
819                    * (y - (ext[dir] + dir * score_param->FREE_HEAD_DISTANCE))
820                    <? 0)
821                 / infos.size ();
822             }
823         }
824
825 #if DEBUG_SLUR_QUANTING
826       (*scores)[i].score_card_ += to_string ("C%.2f", demerit);
827 #endif
828
829       (*scores)[i].score_ += demerit;
830     }
831 }
832
833 void
834 score_extra_encompass (Grob *me, Grob *common[],
835                        Slur_score_parameters *score_param,
836                        Drul_array<Bound_info> extremes,
837                        Drul_array<Offset> base_attach,
838                        Array<Slur_score> *scores)
839 {
840   (void) base_attach;
841   (void) extremes;
842
843   Link_array<Grob> encompasses
844     = Pointer_group_interface__extract_grobs (me, (Grob *)0,
845                                               "encompass-objects");
846   Direction dir = get_grob_direction (me);
847   Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
848   Real lt =  me->get_paper ()->get_dimension (ly_symbol2scm ("linethickness"));
849   Real thick = robust_scm2double (me->get_property ("thickness"), 1.0) * lt;
850
851   /* FIXME: use actual height of script?
852
853    ugh: see slur-script.ly
854         must be <= 0.75 for  b-.( b-.  BUT
855         must be >= 0.9 for b-- ( b-)
856
857         both settings break the other.  */
858   Real y_padding = 0.9 * staff_space;
859
860   Array<Real> xs;
861   Array<Interval> yexts;
862   for (int i = 0; i < encompasses.size (); i++)
863     {
864       Grob *g = encompasses [i];
865       Interval xe = g->extent (common[X_AXIS], X_AXIS);
866       Interval ye = g->extent (common[Y_AXIS], Y_AXIS);
867
868       Real xp = 0.0;
869
870       if (Accidental_interface::has_interface (g))
871         {
872           /* Begin copy accidental.cc */
873           bool parens = false;
874           if (to_boolean (g->get_property ("cautionary")))
875             {
876               SCM cstyle = g->get_property ("cautionary-style");
877               parens = ly_c_equal_p (cstyle, ly_symbol2scm ("parentheses"));
878             }
879         
880           SCM accs = g->get_property ("accidentals");
881           SCM scm_style = g->get_property ("style");
882           if (!ly_c_symbol_p (scm_style)
883               && !parens
884               && scm_ilength (accs) == 1)
885             {
886               /* End copy accidental.cc */
887               switch (ly_scm2int (ly_car (accs)))
888                 {
889                 case FLAT:
890                 case DOUBLE_FLAT:
891                   xp = LEFT;
892                   /* fallthrough */
893                 case SHARP:
894                   xp = 0.5 * dir;
895                   /* fallthrough */
896                 case NATURAL:
897                   xp = -dir;
898                 }
899             }
900         }
901
902       xs.push (xe.linear_combination (xp));
903       ye.widen (thick * 0.5);
904       ye[dir] += dir * y_padding;
905       yexts.push (ye);
906     }
907
908   for (int i = 0; i < scores->size (); i++)
909     {
910       Bezier const &bez (scores->elem (i).curve_);
911       Real demerit = 0.0;
912       for (int j = 0; j < xs.size(); j++)
913         {
914           Real x = xs[j];
915           if ((x < scores->elem (i).attachment_[RIGHT][X_AXIS]
916                && x > scores->elem (i).attachment_[LEFT][X_AXIS]))
917             {   
918               Real y = bez.get_other_coordinate (X_AXIS, x);
919               if (yexts[j].contains (y))
920                 {
921                   if (Accidental_interface::has_interface (encompasses[j]))
922                     demerit += score_param->ACCIDENTAL_COLLISION;
923                   else
924                     demerit += score_param->EXTRA_OBJECT_COLLISION;
925                 }
926             }
927         }
928 #if DEBUG_SLUR_QUANTING
929       (*scores)[i].score_card_ += to_string ("X%.2f", demerit);
930 #endif
931       (*scores)[i].score_ += demerit;
932     }
933 }
934
935 void
936 score_edges (Grob *me, Grob *common[],
937              Slur_score_parameters * score_param,
938              Drul_array<Bound_info> extremes,
939              Drul_array<Offset> base_attach,
940              Array<Slur_score> *scores)
941 {
942   (void) common;
943   Direction dir = get_grob_direction (me);
944
945   for (int i = 0; i < scores->size (); i++)
946     {
947       Direction d = LEFT;
948       do
949         {
950           Real y = scores->elem (i).attachment_[d][Y_AXIS];
951           Real dy = fabs (y - base_attach[d][Y_AXIS]);
952         
953           Real factor = score_param->EDGE_ATTRACTION_FACTOR;
954           Real demerit = factor * dy;
955           if (extremes[d].stem_
956               && extremes[d].stem_dir_ == dir
957               && !Stem::get_beaming (extremes[d].stem_, -d)
958               )
959             demerit /= 5;
960         
961           (*scores)[i].score_ += demerit;
962 #if DEBUG_SLUR_QUANTING
963           (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
964 #endif
965         }
966       while (flip (&d) != LEFT);
967     }
968 }
969
970 void
971 score_slopes (Grob *me, Grob *common[],
972               Slur_score_parameters *score_param,
973               Drul_array<Bound_info> extremes,
974               Drul_array<Offset> base_attach,
975               Array<Slur_score> * scores)
976 {
977   (void) me;
978   (void) base_attach;
979
980   Drul_array<Real> ys;
981   Direction d = LEFT;
982   do
983     {
984       if (extremes[d].slur_head_)
985         ys[d] = extremes[d].slur_head_->relative_coordinate (common[Y_AXIS],
986                                                               Y_AXIS);
987       else
988         ys[d] = extremes[d].neighbor_y_;
989     }
990   while (flip (&d) != LEFT);
991
992   bool has_beams
993     = (extremes[LEFT].stem_ && Stem::get_beam (extremes[LEFT].stem_))
994     || (extremes[RIGHT].stem_ && Stem::get_beam (extremes[RIGHT].stem_));
995
996   Real dy = ys[RIGHT] - ys[LEFT];
997   for (int i = 0; i < scores->size (); i++)
998     {
999       Offset slur_dz = (*scores)[i].attachment_[RIGHT]
1000         - (*scores)[i].attachment_[LEFT];
1001       Real slur_dy = slur_dz[Y_AXIS];
1002       Real demerit = 0.0;
1003
1004       demerit += ((fabs (slur_dy / slur_dz[X_AXIS])
1005                    - score_param->MAX_SLOPE) >? 0)
1006         * score_param->MAX_SLOPE_FACTOR;
1007
1008       /* 0.2: account for staffline offset. */
1009       Real max_dy = (fabs (dy) + 0.2);
1010       if (has_beams)
1011         max_dy += 1.0;
1012
1013       demerit += score_param->STEEPER_SLOPE_FACTOR
1014         * ((fabs (slur_dy) -max_dy) >? 0);
1015
1016       demerit += ((fabs (slur_dy/slur_dz[X_AXIS])
1017                    - score_param->MAX_SLOPE) >? 0)
1018         * score_param->MAX_SLOPE_FACTOR;
1019
1020       if (sign (dy) == 0
1021           && sign (slur_dy) != 0)
1022         demerit += score_param->NON_HORIZONTAL_PENALTY;
1023
1024       if (sign (dy)
1025           && sign (slur_dy)
1026           && sign (slur_dy) != sign (dy))
1027         demerit += has_beams
1028           ? score_param->SAME_SLOPE_PENALTY / 10
1029           : score_param->SAME_SLOPE_PENALTY;
1030
1031 #if DEBUG_SLUR_QUANTING
1032       (*scores)[i].score_card_ += to_string ("S%.2f", d);
1033 #endif
1034       (*scores)[i].score_ += demerit;
1035     }
1036 }
1037
1038