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