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