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