]> git.donarmstrong.com Git - lilypond.git/blob - lily/new-slur.cc
fdef450f4f9bf620221e4851493ecf8024197da1
[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       Real y;
278       if (stem
279         && get_grob_direction (stem) == dir
280         && Stem::get_beam (stem))
281         {
282           y = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
283         }
284       else
285         {
286           y = h->extent (common[Y_AXIS], Y_AXIS)[dir];
287         }
288
289       y += dir * 0.5 * staff_space;
290
291       Grob * staff = Staff_symbol_referencer::get_staff_symbol (h);
292       Real pos = 2.0 * (y - staff->relative_coordinate (common[Y_AXIS], Y_AXIS))
293         / Staff_symbol::staff_space (staff);
294
295       
296       /*
297         start off staffline.
298       */
299       if (fabs (pos - round (pos)) < 0.2
300           && Staff_symbol_referencer::on_staffline (h, (int) rint (pos))
301           && Staff_symbol_referencer::line_count (h) -1 >= rint (pos)
302           )
303             y += staff_space * dir / 10 ;         
304
305       
306       
307       Grob * fh = Note_column::first_head (extremes[d]);
308       Real x = fh->extent (common[X_AXIS],  X_AXIS).linear_combination (CENTER);
309
310       if (get_grob_direction (stem) == dir
311           && dir == -d)
312         {
313           x -=  d * fh->extent(fh, X_AXIS).length ();
314         }
315     
316       base_attachment[d] = Offset (x, y);
317
318     } while (flip (&d) != LEFT);
319
320   Drul_array<Real> staff_offsets;
321   Interval end_ys;
322
323   do {
324     staff_offsets[d] = staves[d]->relative_coordinate (common[Y_AXIS], Y_AXIS);
325     end_ys[d] =  dir * ((dir * (base_attachment[d][Y_AXIS] + 4.0 *dir)) >?
326                         (dir * (dir + extremes[d]->extent(common[Y_AXIS],Y_AXIS)[dir])));
327   } while (flip (&d) != LEFT);
328   
329   Array<Slur_score> scores;
330   
331   Drul_array<Offset> os;
332
333   /*ugh.   */
334   os[LEFT] = base_attachment[LEFT];
335     
336   for (int i = 0; dir * os[LEFT][Y_AXIS] < dir * end_ys[LEFT]; i++)
337     {
338       os[RIGHT] = base_attachment[RIGHT];
339       for (int j = 0; dir *os[RIGHT][Y_AXIS] < dir * end_ys[RIGHT]; j++)
340         {
341           Slur_score s;
342           s.attachment_ = os;
343
344           scores.push (s);
345
346           Real incr = dir * staff_space;
347           if  (Staff_symbol_referencer::staff_radius (staves[RIGHT])
348               < fabs ((os[RIGHT][Y_AXIS] - staff_offsets[RIGHT]) / staff_space))
349             incr /= 2;
350
351           os[RIGHT][Y_AXIS] += incr;      
352         }
353
354       Real incr = dir * staff_space;
355       if  (Staff_symbol_referencer::staff_radius (staves[LEFT])
356            < fabs ((os[LEFT][Y_AXIS] - staff_offsets[LEFT]) / staff_space))
357         incr /= 2;
358
359       os[LEFT][Y_AXIS] += incr;   
360     }
361   
362   {
363     Real r_0 = robust_scm2double (me->get_property ("ratio"), 1);
364     Real h_inf = staff_space * ly_scm2double (me->get_property ("height-limit"));
365     for (int i = scores.size(); i-- ;)
366       {
367         scores[i].curve_ = get_bezier (me, scores[i].attachment_,
368                                        r_0, h_inf);
369       }
370   }
371   
372   score_encompass (me, common, base_attachment, &scores);
373   score_slopes (me, common, base_attachment, &scores);
374
375   Real opt = 1e6;
376   int opt_idx = 0;
377   for (int i = scores.size (); i--;)
378     {
379       if (scores[i].score_  < opt)
380         {
381           opt = scores[i].score_;
382           opt_idx = i;
383         }
384     }
385   
386   Bezier const &b =  scores[opt_idx].curve_;
387   
388   SCM controls = SCM_EOL;
389   for (int i = 4; i--;)
390     {
391       Offset o = b.control_[i] -
392         Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
393                 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
394       
395       controls = scm_cons (ly_offset2scm (o), controls);
396     }
397
398   me->set_property ("control-points", controls);
399
400 #if DEBUG_SLUR_QUANTING
401  scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
402       
403  // debug quanting
404  me->set_property ("quant-score",
405                    scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
406 #endif
407   
408 }
409
410 void
411 New_slur::score_encompass (Grob * me,  Grob *common[], Drul_array<Offset> base_attach,
412                            Array<Slur_score> * scores)
413 {
414   Link_array<Grob> encompasses =
415     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
416   Direction dir = get_grob_direction (me);
417  
418   Array<Encompass_info> infos;
419   Drul_array<Grob *> extremes (encompasses[0], encompasses.top ());
420
421   int first = 1;
422   int last = encompasses.size () - 2;
423
424   for (int i = first; i <= last; i++)
425     infos.push (get_encompass_info (me, encompasses[i], common));
426
427   Drul_array<Grob*> stems;
428   Direction d = LEFT;
429   do {
430     Grob *stem = Note_column::get_stem (extremes [d]);
431     stems[d] = stem;
432   } while (flip (&d) != LEFT);
433
434
435  
436   for (int i =0 ; i < scores->size (); i++)
437     {
438       Bezier const &bez (scores->elem (i).curve_);
439       Real demerit =0.;
440       for (int j = 0; j < infos.size(); j++)
441         {
442           Real x = infos[j].x_;
443
444           if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
445                 &&x > scores->elem (i).attachment_[LEFT][X_AXIS]))
446             continue;
447           
448           Real y = bez.get_other_coordinate (X_AXIS, x);
449
450           if (dir * (y - infos[j].head_) < 0)
451             demerit += HEAD_ENCOMPASS_PENALTY;
452           
453           if (dir * (y - infos[j].stem_) < 0)
454             demerit += STEM_ENCOMPASS_PENALTY;
455           else
456             {
457               Interval ext;
458               ext.add_point (infos[j].stem_);
459               ext.add_point (infos[j].head_);
460
461               demerit += - CLOSENESS_FACTOR * (dir * (y - (ext[dir] + dir * HEAD_FREE_SPACE)) <? 0) /
462                 infos.size ();
463             }
464         }
465
466       Direction d = LEFT;
467       do {
468
469         Real attr =
470           EDGE_ATTRACTION_FACTOR
471           * fabs (scores->elem (i).attachment_[d][Y_AXIS] - base_attach[d][Y_AXIS]);
472         if (get_grob_direction (stems[d]) == dir)
473           attr /= 5;
474         
475         demerit += attr;
476       } while (flip (&d) != LEFT);
477
478 #if DEBUG_SLUR_QUANTING
479       (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
480 #endif
481       
482       (*scores)[i].score_ += demerit;
483     }
484 }
485
486
487 void
488 New_slur::score_slopes (Grob * me,  Grob *common[], Drul_array<Offset> base_attach,
489                         Array<Slur_score> * scores)
490 {
491  Link_array<Grob> columns =
492     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
493
494   Drul_array<Grob *> extremes (columns[0], columns.top ());
495   Direction dir = get_grob_direction (me);
496   Drul_array<Real> ys;
497
498   Direction d = LEFT;
499   Drul_array<Direction> stem_dirs;
500   Drul_array<bool> beams;
501   do {
502     Grob *stem = Note_column::get_stem (extremes [d]);
503     ys[d] = Stem::extremal_heads (stem)[Direction (dir)]
504       ->relative_coordinate (common[Y_AXIS], Y_AXIS);
505
506     stem_dirs[d] = get_grob_direction (stem);
507     beams[d] = Stem::get_beam (stem);
508   } while (flip (&d) != LEFT);
509
510   Real dx = extremes[RIGHT]->relative_coordinate (common[X_AXIS],X_AXIS)
511     - extremes[LEFT]->relative_coordinate (common[X_AXIS],X_AXIS);
512
513   Real dy = ys[RIGHT] - ys[LEFT];
514   for (int i =0 ; i < scores->size (); i++)
515     {
516       Real slur_dy = (*scores)[i].attachment_[RIGHT][Y_AXIS]
517         -  (*scores)[i].attachment_[LEFT][Y_AXIS];
518
519       Real demerit = 0.0;
520
521
522       if(! (beams[LEFT] || beams[RIGHT]))
523         demerit += STEEPER_SLOPE_FACTOR *  (dir * (fabs (slur_dy) - fabs (dy)) >? 0);
524
525       demerit += ((fabs (slur_dy/dx) - MAX_SLOPE)>?0)  * MAX_SLOPE_FACTOR;
526       
527       if (sign (dy) == 0 &&
528           sign (slur_dy) != 0)
529         demerit += NON_HORIZONTAL_PENALTY;
530
531         
532
533       if (sign (dy)
534           && sign (slur_dy)
535           && sign (slur_dy) != sign (dy))
536         demerit +=
537           (beams[LEFT] || beams[RIGHT])
538           ? SAME_SLOPE_PENALTY/10 : SAME_SLOPE_PENALTY;
539       
540 #if DEBUG_SLUR_QUANTING
541       (*scores)[i].score_card_ += to_string ("S%.2f",d);
542 #endif
543       (*scores)[i].score_ += demerit;
544     }
545 }
546
547
548 Bezier
549 New_slur::get_curve (Grob*me) 
550 {
551   Bezier b;
552   int i = 0;
553   for (SCM s= me->get_property ("control-points"); s != SCM_EOL; s = ly_cdr (s))
554     {
555       b.control_[i++] = ly_scm2offset (ly_car (s));
556     }
557
558   return b;
559 }
560
561
562
563
564
565
566 /*
567   ugh ?
568  */
569 MAKE_SCHEME_CALLBACK (New_slur, height, 2);
570 SCM
571 New_slur::height (SCM smob, SCM ax)
572 {
573   Axis a = (Axis)ly_scm2int (ax);
574   Grob * me = unsmob_grob (smob);
575   assert (a == Y_AXIS);
576
577   SCM mol = me->get_uncached_stencil ();
578   Interval ext;
579   if (Stencil * m = unsmob_stencil (mol))
580     ext = m->extent (a);
581   return ly_interval2scm (ext);
582 }
583
584 /*
585   Ugh should have dash-length + dash-period
586  */
587 MAKE_SCHEME_CALLBACK (New_slur, print,1);
588 SCM
589 New_slur::print (SCM smob)
590 {
591   Grob * me = unsmob_grob (smob);
592   if (!scm_ilength (me->get_property ("note-columns")))
593     {
594       me->suicide ();
595       return SCM_EOL;
596     }
597
598   Real base_thick = robust_scm2double (me->get_property ("thickness"), 1);
599   Real thick = base_thick * Staff_symbol_referencer::line_thickness (me);
600
601   Real ss = Staff_symbol_referencer::staff_space (me);
602   Bezier one = get_curve (me);
603
604   // get_curve may suicide
605   if (!scm_ilength (me->get_property ("note-columns")))
606     return SCM_EOL;
607
608   Stencil a;
609
610   /*
611     TODO: replace dashed with generic property.
612    */
613   SCM d =  me->get_property ("dashed");
614   if (ly_c_number_p (d))
615     a = Lookup::dashed_slur (one, thick, thick * robust_scm2double (d, 0));
616   else
617     a = Lookup::slur (one, get_grob_direction (me) * base_thick * ss / 10.0,
618                       thick);
619
620 #if DEBUG_SLUR_QUANTING
621   SCM quant_score = me->get_property ("quant-score");
622   if (// debug_beam_quanting_flag      &&
623       ly_c_string_p (quant_score))
624     {
625       String str;
626       SCM properties = Font_interface::text_font_alist_chain (me);
627
628       Stencil tm = *unsmob_stencil (Text_item::interpret_markup
629          (me->get_paper ()->self_scm (), properties, quant_score));
630       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0, 0);
631     }
632 #endif
633   
634   return a.smobbed_copy ();
635 }
636
637
638
639
640
641 ADD_INTERFACE (New_slur, "new-slur-interface",
642   "A slur",
643   "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");