]> git.donarmstrong.com Git - lilypond.git/blob - lily/new-slur.cc
(score_slopes): strong sloping score only when
[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 "font-interface.hh"
14 #include "text-item.hh"
15 #include "directional-element-interface.hh"
16 #include "group-interface.hh"
17 #include "lily-guile.hh"
18 #include "lookup.hh"
19 #include "note-column.hh"
20 #include "output-def.hh"
21 #include "rod.hh"
22 #include "slur-bezier-bow.hh"
23 #include "slur.hh"
24 #include "spanner.hh"
25 #include "staff-symbol-referencer.hh"
26 #include "staff-symbol.hh"
27 #include "stem.hh"
28 #include "stencil.hh"
29 #include "warn.hh"
30
31 /*
32   TODO:
33
34   - avoid collision with staff line
35   - curve around flag/stem for x coordinate
36   - better scoring.
37   
38  */
39
40 struct Encompass_info {
41   Real x_;
42   Real stem_;
43   Real head_;
44   Encompass_info ()
45   {
46     x_ = 0.0;
47     stem_ = 0.0;
48     head_ = 0.0;
49   }
50 };
51
52 /*
53   TODO: put in details list.
54   */
55 const int SLUR_REGION_SIZE = 5;
56 const Real HEAD_ENCOMPASS_PENALTY = 1000.0;
57 const Real STEM_ENCOMPASS_PENALTY = 30.0;
58 const Real CLOSENESS_FACTOR = 10;
59 const Real EDGE_ATTRACTION_FACTOR = 4; 
60 const Real HEAD_FREE_SPACE = 0.3;
61 const Real SAME_SLOPE_PENALTY = 20;
62 const Real STEEPER_SLOPE_FACTOR = 50;
63 const Real NON_HORIZONTAL_PENALTY = 15;
64 const Real HEAD_STRICT_FREE_SPACE = 0.2;
65 const Real MAX_SLOPE = 1.4;
66 const Real MAX_SLOPE_FACTOR = 10;
67
68
69 #define DEBUG_SLUR_QUANTING 1
70
71 struct Slur_score {
72   Drul_array<Offset> attachment_;
73   Real score_;
74   Bezier curve_;
75
76 #if DEBUG_SLUR_QUANTING
77   String score_card_;
78 #endif
79
80   Slur_score() {
81     score_ = 0.0;
82   }
83 };
84
85 class New_slur
86 {
87 public:
88   static void add_column (Grob *me, Grob *col);
89   DECLARE_SCHEME_CALLBACK (print, (SCM));
90   static void score_slopes (Grob * me,  Grob *common[], Drul_array<Offset> base_attach,
91                             Array<Slur_score> * scores);
92   
93   static  void score_encompass (Grob * me,  Grob *common[],
94                                 Drul_array<Offset>, Array<Slur_score> * scores);
95   static void set_interface (Grob*);
96   static bool  has_interface (Grob*);
97   static Array<Offset> get_encompass_offsets (Grob *me);
98   static Bezier get_curve (Grob *me);
99   static Bezier get_bezier (Grob *me, Drul_array<Offset>,Real,Real);
100   static Direction get_default_dir (Grob *me);
101   DECLARE_SCHEME_CALLBACK (after_line_breaking, (SCM));
102   DECLARE_SCHEME_CALLBACK (height, (SCM,SCM));
103 private:
104   static void set_end_points (Grob*);
105   static Real get_boundary_notecolumn_y (Grob *me, Direction dir);
106   static Offset broken_trend_offset (Grob *me, Direction dir);
107   static Offset get_attachment (Grob *me,Direction dir, Grob **common);
108   static void de_uglyfy (Grob *me,Slur_bezier_bow* bb, Real default_height);
109   static SCM set_extremities (Grob *me);
110   static void set_control_points (Grob *me);
111   static void check_slope (Grob *me);
112   static Encompass_info get_encompass_info (Grob *me, Grob *col, Grob **common);
113 };
114
115 void
116 New_slur::set_interface (Grob*me)
117 {
118   /* Copy to mutable list. */
119   me->set_property ("attachment",
120                     ly_deep_copy (me->get_property ("attachment")));
121 }
122
123 void
124 New_slur::add_column (Grob*me, Grob*n)
125 {
126   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
127   me->add_dependency (n);
128
129   add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (n));
130 }
131
132 Encompass_info
133 New_slur::get_encompass_info (Grob *me,
134                             Grob *col,
135                             Grob **common) 
136 {
137   Grob* stem = unsmob_grob (col->get_property ("stem"));
138
139   Encompass_info ei;
140   
141   Direction dir = get_grob_direction (me);
142   
143   if (!stem)
144     {
145       programming_error ("No stem for note column?");
146       ei.x_ = col->relative_coordinate (common[X_AXIS], X_AXIS);
147       ei.head_ = ei.stem_ = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
148       return ei;  
149     }
150   Direction stem_dir = get_grob_direction (stem);
151
152   if (Grob *head = Note_column::first_head (col))
153     ei.x_ = head->extent (common[X_AXIS], X_AXIS).center ();
154   else
155     ei.x_ = col->extent (common[X_AXIS], X_AXIS).center ();
156
157   Grob * h = Stem::extremal_heads (stem)[Direction (dir)];
158   if (!h)
159     {
160       ei.head_ = ei.stem_ = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
161       return ei;  
162     }
163   
164   ei.head_ = h->extent (common[Y_AXIS], Y_AXIS)[dir];
165   
166   if ((stem_dir == dir)
167       && !stem->extent (stem, Y_AXIS).is_empty ())
168     {
169       ei.stem_ = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
170     }
171   else
172     ei.stem_ = ei.head_;
173
174   return ei;
175 }
176
177
178 Direction
179 New_slur::get_default_dir (Grob*me) 
180 {
181   Link_array<Grob> encompasses =
182     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
183   
184   Direction d = DOWN;
185   for (int i=0; i < encompasses.size (); i ++) 
186     {
187       if (Note_column::dir (encompasses[i]) < 0) 
188         {
189           d = UP;
190           break;
191         }
192     }
193   return d;
194 }
195
196 MAKE_SCHEME_CALLBACK (New_slur, after_line_breaking,1);
197 SCM
198 New_slur::after_line_breaking (SCM smob)
199 {
200   Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
201   if (!scm_ilength (me->get_property ("note-columns")))
202     {
203       me->suicide ();
204       return SCM_UNSPECIFIED;
205     }
206   
207   if (!get_grob_direction (me))
208     set_grob_direction (me, get_default_dir (me));
209
210   
211   if (!Note_column::has_interface (me->get_bound (LEFT))
212       || !Note_column::has_interface (me->get_bound (RIGHT)))
213     me->suicide ();             // fixme.
214   
215   set_end_points (me);
216
217   return SCM_UNSPECIFIED;
218 }
219
220 Bezier
221 New_slur::get_bezier (Grob *me, Drul_array<Offset> extremes,
222                       Real r_0,
223                       Real h_inf)
224 {
225   Array<Offset> encompasses;
226   encompasses.push (extremes[LEFT]);
227   encompasses.push (extremes[RIGHT]);
228   
229   Slur_bezier_bow bb (encompasses,
230                       get_grob_direction (me), h_inf, r_0);
231
232   return bb.get_bezier ();
233 }
234
235 void
236 New_slur::set_end_points (Grob *me)
237 {
238   Link_array<Grob> columns =
239     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
240   
241   if (columns.is_empty ())
242     {
243       me->suicide ();
244       return ; 
245     }
246   Real staff_space = Staff_symbol_referencer::staff_space ((Grob*)me);
247   Drul_array<Grob *> extremes (columns[0], columns.top ());
248   Direction dir = get_grob_direction (me);
249   
250   Drul_array<Offset> base_attachment;
251
252   SCM eltlist = me->get_property ("note-columns");
253   Grob *common[] = {common_refpoint_of_list (eltlist, me, X_AXIS),
254                     common_refpoint_of_list (eltlist, me, Y_AXIS)};
255
256
257   Spanner* sp = dynamic_cast<Spanner*> (me);
258   common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
259   common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (LEFT), X_AXIS);
260
261   Direction d = LEFT;
262   Drul_array<Grob*> staves;
263   
264   do {
265     Grob *stem = Note_column::get_stem (extremes[d]);
266     Grob * h = Stem::extremal_heads (stem)[Direction (dir)];
267     staves[d] = Staff_symbol_referencer::get_staff_symbol (h);
268
269     common[Y_AXIS] = common[Y_AXIS]->common_refpoint (staves[d], Y_AXIS);
270   } while (flip (&d) != LEFT);
271   
272   do
273     {
274       // c&p
275       Grob *stem = Note_column::get_stem (extremes[d]);
276       Grob * h = Stem::extremal_heads (stem)[dir];
277      
278       Real y;
279
280       if (stem
281         && get_grob_direction (stem) == dir
282         && Stem::get_beam (stem))
283         {
284           y = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
285         }
286       else
287         {
288           y = h->extent (common[Y_AXIS], Y_AXIS)[dir];
289         }
290
291       y += dir * 0.5 * staff_space;
292
293       Grob * staff = Staff_symbol_referencer::get_staff_symbol (h);
294       Real pos = 2.0 * (y - staff->relative_coordinate (common[Y_AXIS], Y_AXIS)) / Staff_symbol::staff_space (staff);
295
296       
297             /*
298               start off staffline.
299             */
300       if (fabs (pos - round (pos)) < 0.2
301           && Staff_symbol_referencer::on_staffline (h, (int) rint (pos)))
302             y += staff_space * dir / 10 ;         
303
304       
305       
306       Grob * fh = Note_column::first_head (extremes[d]);
307       Real x = fh->extent (common[X_AXIS],  X_AXIS).linear_combination (CENTER);
308
309       if (get_grob_direction (stem) == dir
310           && dir == -d)
311         {
312           x -=  d * fh->extent(fh, X_AXIS).length ();
313         }
314     
315       base_attachment[d] = Offset (x, y);
316
317     } while (flip (&d) != LEFT);
318
319   Drul_array<Real> staff_offsets;
320   Interval end_ys;
321
322   do {
323     staff_offsets[d] = staves[d]->relative_coordinate (common[Y_AXIS], Y_AXIS);
324     end_ys[d] =  dir * ((dir * (base_attachment[d][Y_AXIS] + 4.0 *dir)) >?
325                         (dir * (dir + extremes[d]->extent(common[Y_AXIS],Y_AXIS)[dir])));
326   } while (flip (&d) != LEFT);
327   
328   Array<Slur_score> scores;
329   
330   Drul_array<Offset> os;
331
332   /*ugh.   */
333   os[LEFT] = base_attachment[LEFT];
334     
335   for (int i = 0; dir * os[LEFT][Y_AXIS] < dir * end_ys[LEFT]; i++)
336     {
337       os[RIGHT] = base_attachment[RIGHT];
338       for (int j = 0; dir *os[RIGHT][Y_AXIS] < dir * end_ys[RIGHT]; j++)
339         {
340           Slur_score s;
341           s.attachment_ = os;
342
343           scores.push (s);
344
345           Real incr = dir * staff_space;
346           if  (Staff_symbol_referencer::staff_radius (staves[RIGHT])
347               < fabs ((os[RIGHT][Y_AXIS] - staff_offsets[RIGHT]) / staff_space))
348             incr /= 2;
349
350           os[RIGHT][Y_AXIS] += incr;      
351         }
352
353       Real incr = dir * staff_space;
354       if  (Staff_symbol_referencer::staff_radius (staves[LEFT])
355            < fabs ((os[LEFT][Y_AXIS] - staff_offsets[LEFT]) / staff_space))
356         incr /= 2;
357
358       os[LEFT][Y_AXIS] += incr;   
359     }
360   
361   {
362     Real r_0 = robust_scm2double (me->get_property ("ratio"), 1);
363     Real h_inf = staff_space * ly_scm2double (me->get_property ("height-limit"));
364     for (int i = scores.size(); i-- ;)
365       {
366         scores[i].curve_ = get_bezier (me, scores[i].attachment_,
367                                        r_0, h_inf);
368       }
369   }
370   
371   score_encompass (me, common, base_attachment, &scores);
372   score_slopes (me, common, base_attachment, &scores);
373
374   Real opt = 1e6;
375   int opt_idx = 0;
376   for (int i = scores.size (); i--;)
377     {
378       if (scores[i].score_  < opt)
379         {
380           opt = scores[i].score_;
381           opt_idx = i;
382         }
383     }
384   
385   Bezier const &b =  scores[opt_idx].curve_;
386   
387   SCM controls = SCM_EOL;
388   for (int i = 4; i--;)
389     {
390       Offset o = b.control_[i] -
391         Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
392                 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
393       
394       controls = scm_cons (ly_offset2scm (o), controls);
395     }
396
397   me->set_property ("control-points", controls);
398
399 #if DEBUG_SLUR_QUANTING
400  scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
401       
402  // debug quanting
403  me->set_property ("quant-score",
404                    scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
405 #endif
406   
407 }
408
409 void
410 New_slur::score_encompass (Grob * me,  Grob *common[], Drul_array<Offset> base_attach,
411                            Array<Slur_score> * scores)
412 {
413   Link_array<Grob> encompasses =
414     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
415   Direction dir = get_grob_direction (me);
416  
417   Array<Encompass_info> infos;
418   Drul_array<Grob *> extremes (encompasses[0], encompasses.top ());
419
420   int first = 1;
421   int last = encompasses.size () - 2;
422
423   for (int i = first; i <= last; i++)
424     infos.push (get_encompass_info (me, encompasses[i], common));
425
426   Drul_array<Grob*> stems;
427   Direction d = LEFT;
428   do {
429     Grob *stem = Note_column::get_stem (extremes [d]);
430     stems[d] = stem;
431   } while (flip (&d) != LEFT);
432
433
434  
435   for (int i =0 ; i < scores->size (); i++)
436     {
437       Bezier const &bez (scores->elem (i).curve_);
438       Real demerit =0.;
439       for (int j = 0; j < infos.size(); j++)
440         {
441           Real x = infos[j].x_;
442
443           if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
444                 &&x > scores->elem (i).attachment_[LEFT][X_AXIS]))
445             continue;
446           
447           Real y = bez.get_other_coordinate (X_AXIS, x);
448
449           if (dir * (y - infos[j].head_) < 0)
450             demerit += HEAD_ENCOMPASS_PENALTY;
451           
452           if (dir * (y - infos[j].stem_) < 0)
453             demerit += STEM_ENCOMPASS_PENALTY;
454           else
455             {
456               Interval ext;
457               ext.add_point (infos[j].stem_);
458               ext.add_point (infos[j].head_);
459
460               demerit += - CLOSENESS_FACTOR * (dir * (y - (ext[dir] + dir * HEAD_FREE_SPACE)) <? 0) /
461                 infos.size ();
462             }
463         }
464
465       Direction d = LEFT;
466       do {
467
468         Real attr =
469           EDGE_ATTRACTION_FACTOR
470           * fabs (scores->elem (i).attachment_[d][Y_AXIS] - base_attach[d][Y_AXIS]);
471         if (get_grob_direction (stems[d]) == dir)
472           attr /= 5;
473         
474         demerit += attr;
475       } while (flip (&d) != LEFT);
476
477 #if DEBUG_SLUR_QUANTING
478       (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
479 #endif
480       
481       (*scores)[i].score_ += demerit;
482     }
483 }
484
485
486 void
487 New_slur::score_slopes (Grob * me,  Grob *common[], Drul_array<Offset> base_attach,
488                         Array<Slur_score> * scores)
489 {
490  Link_array<Grob> columns =
491     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
492
493   Drul_array<Grob *> extremes (columns[0], columns.top ());
494   Direction dir = get_grob_direction (me);
495   Drul_array<Real> ys;
496
497   Direction d = LEFT;
498   Drul_array<Direction> stem_dirs;
499   Drul_array<bool> beams;
500   do {
501     Grob *stem = Note_column::get_stem (extremes [d]);
502     ys[d] = Stem::extremal_heads (stem)[Direction (dir)]
503       ->relative_coordinate (common[Y_AXIS], Y_AXIS);
504
505     stem_dirs[d] = get_grob_direction (stem);
506     beams[d] = Stem::get_beam (stem);
507   } while (flip (&d) != LEFT);
508
509   Real dx = extremes[RIGHT]->relative_coordinate (common[X_AXIS],X_AXIS)
510     - extremes[LEFT]->relative_coordinate (common[X_AXIS],X_AXIS);
511
512   Real dy = ys[RIGHT] - ys[LEFT];
513   for (int i =0 ; i < scores->size (); i++)
514     {
515       Real slur_dy = (*scores)[i].attachment_[RIGHT][Y_AXIS]
516         -  (*scores)[i].attachment_[LEFT][Y_AXIS];
517
518       Real demerit = 0.0;
519
520
521       if(! (beams[LEFT] || beams[RIGHT]))
522         demerit += STEEPER_SLOPE_FACTOR *  (dir * (fabs (slur_dy) - fabs (dy)) >? 0);
523
524       demerit += ((fabs (slur_dy/dx) - MAX_SLOPE)>?0)  * MAX_SLOPE_FACTOR;
525       
526       if (sign (dy) == 0 &&
527           sign (slur_dy) != 0)
528         demerit += NON_HORIZONTAL_PENALTY;
529
530         
531
532       if (sign (dy)
533           && sign (slur_dy)
534           && sign (slur_dy) != sign (dy))
535         demerit +=
536           (beams[LEFT] || beams[RIGHT])
537           ? SAME_SLOPE_PENALTY/10 : SAME_SLOPE_PENALTY;
538       
539 #if DEBUG_SLUR_QUANTING
540       (*scores)[i].score_card_ += to_string ("S%.2f",d);
541 #endif
542       (*scores)[i].score_ += demerit;
543     }
544 }
545
546
547 Bezier
548 New_slur::get_curve (Grob*me) 
549 {
550   Bezier b;
551   int i = 0;
552   for (SCM s= me->get_property ("control-points"); s != SCM_EOL; s = ly_cdr (s))
553     {
554       b.control_[i++] = ly_scm2offset (ly_car (s));
555     }
556
557   return b;
558 }
559
560
561
562
563
564
565 /*
566   ugh ?
567  */
568 MAKE_SCHEME_CALLBACK (New_slur, height, 2);
569 SCM
570 New_slur::height (SCM smob, SCM ax)
571 {
572   Axis a = (Axis)ly_scm2int (ax);
573   Grob * me = unsmob_grob (smob);
574   assert (a == Y_AXIS);
575
576   SCM mol = me->get_uncached_stencil ();
577   Interval ext;
578   if (Stencil * m = unsmob_stencil (mol))
579     ext = m->extent (a);
580   return ly_interval2scm (ext);
581 }
582
583 /*
584   Ugh should have dash-length + dash-period
585  */
586 MAKE_SCHEME_CALLBACK (New_slur, print,1);
587 SCM
588 New_slur::print (SCM smob)
589 {
590   Grob * me = unsmob_grob (smob);
591   if (!scm_ilength (me->get_property ("note-columns")))
592     {
593       me->suicide ();
594       return SCM_EOL;
595     }
596
597   Real base_thick = robust_scm2double (me->get_property ("thickness"), 1);
598   Real thick = base_thick * Staff_symbol_referencer::line_thickness (me);
599
600   Real ss = Staff_symbol_referencer::staff_space (me);
601   Bezier one = get_curve (me);
602
603   // get_curve may suicide
604   if (!scm_ilength (me->get_property ("note-columns")))
605     return SCM_EOL;
606
607   Stencil a;
608
609   /*
610     TODO: replace dashed with generic property.
611    */
612   SCM d =  me->get_property ("dashed");
613   if (ly_c_number_p (d))
614     a = Lookup::dashed_slur (one, thick, thick * robust_scm2double (d, 0));
615   else
616     a = Lookup::slur (one, get_grob_direction (me) * base_thick * ss / 10.0,
617                       thick);
618
619 #if DEBUG_SLUR_QUANTING
620   SCM quant_score = me->get_property ("quant-score");
621   if (// debug_beam_quanting_flag      &&
622       ly_c_string_p (quant_score))
623     {
624       String str;
625       SCM properties = Font_interface::text_font_alist_chain (me);
626
627       Stencil tm = *unsmob_stencil (Text_item::interpret_markup
628          (me->get_paper ()->self_scm (), properties, quant_score));
629       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0, 0);
630     }
631 #endif
632   
633   return a.smobbed_copy ();
634 }
635
636
637
638
639
640 ADD_INTERFACE (New_slur, "new-slur-interface",
641   "A slur",
642   "attachment attachment-offset beautiful control-points dashed details de-uglify-parameters direction extremity-function extremity-offset-alist height-limit note-columns ratio slope-limit thickness y-free");