]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie-formatting-problem.cc
Patch for auto-beam; Fix issue 511
[lilypond.git] / lily / tie-formatting-problem.cc
1 /*
2   tie-formatting-problem.cc -- implement Tie_formatting_problem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2005--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7
8 */
9
10 #include "tie-formatting-problem.hh"
11
12 #include "axis-group-interface.hh"
13 #include "paper-column.hh"
14 #include "bezier.hh"
15 #include "directional-element-interface.hh"
16 #include "libc-extension.hh"
17 #include "misc.hh"
18 #include "note-head.hh"
19 #include "rhythmic-head.hh"
20 #include "spanner.hh"
21 #include "staff-symbol-referencer.hh"
22 #include "stem.hh"
23 #include "tie-configuration.hh"
24 #include "tie.hh"
25 #include "warn.hh"
26 #include "pointer-group-interface.hh"
27 #include "output-def.hh"
28
29 void
30 Tie_formatting_problem::print_ties_configuration (Ties_configuration const *ties)
31 {
32   for (vsize i = 0; i < ties->size (); i++)
33     {
34       char const *man_pos = (specifications_[i].has_manual_position_) ? "(M)" : "";
35       char const *man_dir = (specifications_[i].has_manual_dir_) ? "(M)" : "";
36       char const *dir = (ties->at (i).dir_ == UP) ? "up" : "dn";
37       
38       printf ("(P%d%s, %s%s) ", ties->at (i).position_, man_pos, dir, man_dir);
39     }
40   printf ("\n");
41 }
42
43 Interval
44 Tie_formatting_problem::get_attachment (Real y, Drul_array<int> columns) const
45 {
46   Interval attachments (0,0);
47   Direction d = LEFT;
48   do
49     {
50       Tuple2<int> key (columns[d], int (d));
51       Chord_outline_map::const_iterator i (chord_outlines_.find (key));
52       if (i == chord_outlines_.end ())
53         programming_error ("Cannot find chord outline");
54       else
55         attachments[d] = i->second.height (y);
56     }
57   while (flip (&d) != LEFT);
58   
59   return attachments;
60 }
61
62 Tie_formatting_problem::Tie_formatting_problem ()
63 {
64   x_refpoint_ = 0;
65   y_refpoint_ = 0;
66   use_horizontal_spacing_ = true;
67 }
68
69 Tie_formatting_problem::~Tie_formatting_problem ()
70 {
71   for (Tie_configuration_map::const_iterator i (possibilities_.begin ());
72        i != possibilities_.end (); i++)
73     delete (*i).second;
74 }
75
76 void
77 Tie_formatting_problem::set_column_chord_outline (vector<Item*> bounds,
78                                                   Direction dir,
79                                                   int column_rank)
80 {
81   Real staff_space = Staff_symbol_referencer::staff_space (bounds[0]);
82
83   vector<Box> boxes;
84   vector<Box> head_boxes;
85
86   Grob *stem = 0;
87   for (vsize i = 0; i < bounds.size (); i++)
88     {
89       Grob *head = bounds[i];
90       if (!Note_head::has_interface (head))
91         continue;
92           
93       if (!stem)
94         stem = unsmob_grob (head->get_object ("stem"));
95           
96       Real p = Staff_symbol_referencer::get_position (head);
97       Interval y ((p-1) * 0.5 * staff_space,
98                   (p+1) * 0.5 * staff_space);
99
100       Interval x = head->extent (x_refpoint_, X_AXIS);
101       head_boxes.push_back (Box (x, y));
102       boxes.push_back (Box (x, y));
103
104       Grob *dots = Rhythmic_head::get_dots (head);
105       if (dir == LEFT && dots)
106         {
107           Interval x = dots->extent (x_refpoint_, X_AXIS);
108           int p = int (Staff_symbol_referencer::get_position (dots));
109
110           /*
111             TODO: shouldn't this use column-rank dependent key?  
112           */
113           dot_positions_.insert (p);
114           dot_x_.unite (x);
115
116           Interval y (dots->extent (dots, Y_AXIS));
117           y.translate (p * staff_space * 0.5);
118           
119           boxes.push_back (Box (x, y));
120         }
121     }
122
123   Tuple2<int> key (column_rank, int (dir));
124
125   if (stem)
126     {
127       if (Stem::is_normal_stem (stem))
128         {
129           Interval x;
130           x.add_point (stem->relative_coordinate (x_refpoint_, X_AXIS));
131           x.widen (staff_space / 20); // ugh.
132           Interval y;
133
134           Real stem_end_position = 0.0;
135           if (Stem::is_cross_staff (stem))
136             stem_end_position =  get_grob_direction (stem) * infinity_f;
137           else
138             {
139               if (use_horizontal_spacing_ || !Stem::get_beam (stem))
140                 stem_end_position = Stem::stem_end_position (stem) * staff_space * .5;
141               else
142                 stem_end_position = Stem::note_head_positions (stem)[get_grob_direction (stem)]
143                   * staff_space * .5;
144             }
145           
146           y.add_point (stem_end_position);
147
148           Direction stemdir = get_grob_direction (stem);
149           y.add_point (Stem::head_positions (stem)[-stemdir]
150                        * staff_space * .5);
151
152           /*
153             add extents of stem.
154           */
155           boxes.push_back (Box (x, y));
156
157           stem_extents_[key].unite (Box (x, y));
158
159           if (dir == LEFT)
160             {
161               Box flag_box = Stem::get_translated_flag (stem).extent_box ();
162               flag_box.translate ( Offset (x[RIGHT], X_AXIS));
163               boxes.push_back (flag_box);
164             }
165         }
166       else
167         {
168           Grob *head = Stem::support_head (stem);
169
170           /*
171             In case of invisible stem, don't pass x-center of heads.
172           */
173           Real x_center = head->extent (x_refpoint_, X_AXIS).center ();
174           Interval x_ext;
175           x_ext[-dir] = x_center;
176           x_ext[dir] = infinity_f * dir;
177           Interval y_ext;
178           for (vsize j = 0; j < head_boxes.size (); j++)
179             y_ext.unite (head_boxes[j][Y_AXIS]);
180
181           boxes.push_back (Box (x_ext, y_ext));
182         }
183
184       extract_grob_set (stem, "note-heads", heads);
185       for (vsize i = 0; i < heads.size (); i ++)
186         {
187           if (find (bounds.begin (), bounds.end (), dynamic_cast<Item*> (heads[i])) ==  bounds.end ())
188             {
189               /*
190                 other untied notes in the same chord.
191               */
192           
193               Interval y = Staff_symbol_referencer::extent_in_staff (heads[i]);
194               Interval x = heads[i]->extent (x_refpoint_, X_AXIS);
195               boxes.push_back (Box (x, y));
196             }
197           
198           Grob *acc = unsmob_grob (heads[i]->get_object ("accidental-grob"));
199           if (acc)
200             acc->get_property ("stencil"); /* trigger tie-related suicide */
201
202           if (acc && acc->is_live () && dir == RIGHT)
203             {
204               boxes.push_back (Box (acc->extent (x_refpoint_, X_AXIS),
205                                     Staff_symbol_referencer::extent_in_staff (acc)));
206             }
207
208           head_positions_[column_rank].add_point (int (Staff_symbol_referencer::get_position (heads[i])));
209         }
210       
211     }
212   
213   Direction updowndir = DOWN;
214   do
215     {
216       Interval x;
217       Interval y;
218       if (head_boxes.size ())
219         {
220           Box b = boundary (head_boxes, updowndir, 0);
221           x = b[X_AXIS];
222           x[-dir] =  b[X_AXIS].linear_combination (-dir / 2);
223           y[-updowndir] = b[Y_AXIS][updowndir];
224           y[updowndir] = updowndir * infinity_f;
225         }
226
227       if (!x.is_empty ())
228         boxes.push_back (Box (x, y));
229     }
230   while (flip (&updowndir) != DOWN);
231
232   /* todo: the horizon_padding is somewhat arbitrary */
233   chord_outlines_[key] = Skyline (boxes, details_.skyline_padding_, Y_AXIS, -dir);
234   if (bounds[0]->break_status_dir ())
235     {
236       Interval iv (Axis_group_interface::staff_extent (bounds[0], x_refpoint_, X_AXIS, y_refpoint_, Y_AXIS));
237       if (iv.is_empty ())
238         iv.add_point (bounds[0]->relative_coordinate (x_refpoint_, X_AXIS));
239
240       chord_outlines_[key].set_minimum_height (iv[-dir]);
241     }
242   else
243     {
244       Interval x;
245       for (vsize j = 0; j < head_boxes.size (); j++)
246         {
247           x.unite (head_boxes[j][X_AXIS]);
248         }
249       
250       chord_outlines_[key].set_minimum_height (x[dir]);
251     }
252
253   head_extents_[key].set_empty ();
254   for (vsize i = 0; i < head_boxes.size (); i++)
255     {
256       head_extents_[key].unite (head_boxes[i]);
257     }
258 }
259
260 void
261 Tie_formatting_problem::set_chord_outline (vector<Item*> bounds,
262                                            Direction dir)
263
264 {
265   vector<int> ranks;
266   for (vsize i = 0; i < bounds.size (); i++)
267     ranks.push_back (bounds[i]->get_column ()->get_rank ());
268
269   vector_sort (ranks, less<int> ());
270   uniq (ranks);
271
272   for (vsize i = 0; i < ranks.size (); i++)
273     {
274       vector<Item*> col_items;
275       for (vsize j = 0; j < bounds.size (); j ++)
276         {
277           if (bounds[j]->get_column ()->get_rank () == ranks[i])
278             col_items.push_back (bounds[j]);
279         }
280
281       set_column_chord_outline (col_items, dir, ranks[i]);
282     }
283 }
284   
285
286
287 void
288 Tie_formatting_problem::from_tie (Grob *tie)
289 {
290   vector<Grob*> ties;
291   ties.push_back (tie);
292   from_ties (ties);
293
294   details_.from_grob (tie);
295 }
296
297 Grob *
298 Tie_formatting_problem::common_x_refpoint () const
299 {
300   return x_refpoint_;
301 }
302
303 void
304 Tie_formatting_problem::from_ties (vector<Grob*> const &ties)
305 {
306   if (ties.empty ())
307     return;
308   
309   x_refpoint_ = ties[0];
310   y_refpoint_ = ties[0];
311   for (vsize i = 0; i < ties.size (); i++)
312     {
313       Spanner *tie = dynamic_cast<Spanner*> (ties[i]);
314       Item *l = tie->get_bound (LEFT);
315       Item *r = tie->get_bound (RIGHT);
316
317       x_refpoint_ = l->common_refpoint (x_refpoint_, X_AXIS); 
318       x_refpoint_ = r->common_refpoint (x_refpoint_, X_AXIS);
319
320       if (!l->break_status_dir ())
321         y_refpoint_ = l->common_refpoint (y_refpoint_, Y_AXIS); 
322       if (!r->break_status_dir ())
323         y_refpoint_ = r->common_refpoint (y_refpoint_, Y_AXIS); 
324     }
325
326   details_.from_grob (ties[0]);
327   
328   Direction d = LEFT;
329   do
330     {
331       vector<Item*> bounds;
332       
333       for (vsize i = 0; i < ties.size (); i++)
334         {
335           Item *it = dynamic_cast<Spanner*> (ties[i])->get_bound (d);
336           if (it->break_status_dir ())
337               it = it->get_column ();
338
339           bounds.push_back (it);
340         }
341       
342       set_chord_outline (bounds, d);
343     }
344   while (flip (&d) != LEFT);
345
346
347   for (vsize i = 0; i < ties.size (); i++)
348     {
349       Tie_specification spec;
350       spec.from_grob (ties[i]);
351       
352       do
353         {
354           spec.note_head_drul_[d] = Tie::head (ties[i], d);
355           spec.column_ranks_[d] = Tie::get_column_rank (ties[i], d);
356         }
357       while (flip (&d) != LEFT);
358       specifications_.push_back (spec);
359     }
360 }
361
362 void
363 Tie_formatting_problem::from_semi_ties (vector<Grob*> const &semi_ties, Direction head_dir)
364 {
365   if (semi_ties.empty ())
366     return;
367
368   use_horizontal_spacing_ = false; 
369   details_.from_grob (semi_ties[0]);
370   vector<Item*> heads;
371
372   int column_rank = -1;
373   for (vsize i = 0; i < semi_ties.size (); i++)
374     {
375       Tie_specification spec;
376       Item *head = unsmob_item (semi_ties[i]->get_object ("note-head"));
377        
378       if (!head)
379         programming_error ("LV tie without head?!");
380
381       if (head)
382         {
383           spec.position_ = int (Staff_symbol_referencer::get_position (head));
384         }
385
386       spec.from_grob (semi_ties[i]);
387       
388       spec.note_head_drul_[head_dir] = head;
389       column_rank = Tie::get_column_rank (semi_ties[i], head_dir);
390       spec.column_ranks_ = Drul_array<int> (column_rank, column_rank);
391       heads.push_back (head);
392       specifications_.push_back (spec);
393     }
394
395   x_refpoint_ = semi_ties[0];
396   y_refpoint_ = semi_ties[0];
397
398   for (vsize i = 0; i < semi_ties.size (); i++)
399     {
400       x_refpoint_ = semi_ties[i]->common_refpoint (x_refpoint_, X_AXIS); 
401       y_refpoint_ = semi_ties[i]->common_refpoint (y_refpoint_, Y_AXIS); 
402     }
403   for (vsize i = 0; i < heads.size (); i++)
404     {
405       x_refpoint_ = heads[i]->common_refpoint (x_refpoint_, X_AXIS); 
406       y_refpoint_ = heads[i]->common_refpoint (y_refpoint_, Y_AXIS) ;
407     }
408
409   set_chord_outline (heads, head_dir);
410
411   Tuple2<int> head_key (column_rank, head_dir);
412   Tuple2<int> open_key (column_rank, -head_dir);
413   Real extremal = chord_outlines_[head_key].max_height ();
414
415   chord_outlines_[open_key] = Skyline (head_dir);
416   chord_outlines_[open_key].set_minimum_height (extremal - head_dir * 1.5);
417 }
418
419
420 Tie_specification
421 Tie_formatting_problem::get_tie_specification (int i) const
422 {
423   return specifications_[i];
424 }
425
426
427 /*
428   Return configuration, create it if necessary. 
429 */
430 Tie_configuration*
431 Tie_formatting_problem::get_configuration (int pos, Direction dir, Drul_array<int> columns,
432                                            bool tune_dy) const
433 {
434   int key_components[] = {
435     pos, dir, columns[LEFT], columns[RIGHT]
436   };
437   Tuple<int,4> key (key_components);
438     
439   Tie_configuration_map::const_iterator f = possibilities_.find (key);
440   if (f != possibilities_.end ())
441     {
442       return (*f).second;
443     }
444
445   
446   Tie_configuration *conf = generate_configuration (pos, dir, columns, tune_dy);
447   ((Tie_formatting_problem*) this)->possibilities_[key] = conf;
448   return conf;
449 }
450
451 Tie_configuration*
452 Tie_formatting_problem::generate_configuration (int pos, Direction dir,
453                                                 Drul_array<int> columns, bool y_tune) const
454 {
455   Tie_configuration *conf = new Tie_configuration;
456   conf->position_ = pos;
457   conf->dir_ = dir;
458   
459   conf->column_ranks_ = columns;
460   
461   Real y = conf->position_ * 0.5 * details_.staff_space_;
462
463   if (dot_positions_.find (pos) != dot_positions_.end ())
464     {
465       conf->delta_y_ += dir * 0.25 * details_.staff_space_;
466       y_tune = false;
467     }
468   
469   if (y_tune
470       && max (fabs (get_head_extent (columns[LEFT], LEFT, Y_AXIS)[dir] - y),
471               fabs (get_head_extent (columns[RIGHT], RIGHT, Y_AXIS)[dir] - y)) < 0.25
472       && !Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, pos))
473     {
474       conf->delta_y_ =
475         (get_head_extent (columns[LEFT], LEFT, Y_AXIS)[dir] - y)
476         + dir * details_.outer_tie_vertical_gap_;
477     }
478
479   if (y_tune)
480     {
481       conf->attachment_x_ = get_attachment (y + conf->delta_y_, conf->column_ranks_);
482       Real h =  conf->height (details_);
483       
484       /*
485         TODO:
486
487         - should make sliding criterion, should flatten ties if
488
489         - they're just the wrong (ie. touching line at top & bottom)
490         size.
491         
492        */
493       if (head_positions_slice (columns[LEFT]).contains (pos)
494           || head_positions_slice (columns[RIGHT]).contains (pos)
495           || abs (pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
496         {
497           if (h < details_.intra_space_threshold_ * 0.5 * details_.staff_space_)
498             {
499               if (!Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, pos)
500                   && abs (pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
501                 {
502                   conf->center_tie_vertically (details_);
503                 }
504               else if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, pos))
505                 {
506                   conf->delta_y_ += dir *
507                     details_.tip_staff_line_clearance_ * 0.5 *  details_.staff_space_;
508                 }
509             }
510           else 
511             {
512               Real top_y = y + conf->delta_y_ + conf->dir_ * h;
513               Real top_pos = top_y / (0.5*details_.staff_space_);
514               int round_pos = int (my_round (top_pos));
515
516               /* TODO: should use other variable? */
517               Real clearance = details_.center_staff_line_clearance_;
518               if (fabs (top_pos - round_pos) < clearance
519                   && Staff_symbol_referencer::on_staff_line (details_.staff_symbol_referencer_,
520                                                              round_pos))
521                 {
522                   Real new_y = (round_pos + clearance * conf->dir_) * 0.5 * details_.staff_space_;
523                   conf->delta_y_ = (new_y - top_y);
524                 }
525             }
526         }
527     }  
528   conf->attachment_x_ = get_attachment (y + conf->delta_y_, conf->column_ranks_);
529   if (conf->height (details_) < details_.intra_space_threshold_ * 0.5 * details_.staff_space_)
530     {
531       /*
532         This is less sensible for long ties, since those are more
533         horizontal.
534       */
535       Interval close_by = get_attachment (y
536                                           + conf->delta_y_
537                                           + (dir * details_.intra_space_threshold_ * 0.25
538                                              * details_.staff_space_),
539                                           conf->column_ranks_);
540       
541       conf->attachment_x_.intersect (close_by);
542     }
543
544   conf->attachment_x_.widen ( - details_.x_gap_);
545
546   if (conf->column_span_length ())
547     {
548       /*
549         avoid the stems that we attach to as well. We don't do this
550         for semities (span length = 0)
551
552         It would be better to check D against HEAD-DIRECTION if
553         applicable.
554       */
555       Direction d = LEFT;
556       do
557         {
558           Real y = conf->position_ * details_.staff_space_ * 0.5 + conf->delta_y_;
559           if (get_stem_extent (conf->column_ranks_[d], d, X_AXIS).is_empty ()
560               || !get_stem_extent (conf->column_ranks_[d], d, Y_AXIS).contains (y))
561             continue;
562
563           conf->attachment_x_[d] =
564             d * min (d * conf->attachment_x_[d],
565                      d * (get_stem_extent (conf->column_ranks_[d], d, X_AXIS)[-d] - d * details_.stem_gap_));
566         }
567       while (flip (&d) != LEFT);
568     }  
569   return conf;
570 }
571
572 Interval
573 Tie_formatting_problem::get_head_extent (int col, Direction d, Axis a) const
574 {
575   Column_extent_map::const_iterator i = head_extents_.find (Tuple2<int> (col, int (d)));
576   if (i != head_extents_.end ())
577     return (*i).second[a];
578   else
579     return Interval ();
580 }
581
582 Interval
583 Tie_formatting_problem::get_stem_extent (int col, Direction d, Axis a) const
584 {
585   Column_extent_map::const_iterator i = stem_extents_.find (Tuple2<int> (col, int (d)));
586   if (i != stem_extents_.end ())
587     return (*i).second[a];
588   else
589     return Interval ();
590 }
591
592 /**
593    TIE_IDX and TIES_CONF are optional.
594  */
595 Real
596 Tie_formatting_problem::score_aptitude (Tie_configuration *conf,
597                                         Tie_specification const &spec,
598                                         Ties_configuration *ties_conf, int tie_idx) const
599 {
600   Real penalty = 0.0;
601   Real curve_y = conf->position_ * details_.staff_space_ * 0.5 + conf->delta_y_;
602   Real tie_y = spec.position_ * details_.staff_space_ * 0.5;
603   if (sign (curve_y - tie_y) != conf->dir_)
604     {
605       Real p =  details_.wrong_direction_offset_penalty_;
606       if (ties_conf)
607         ties_conf->add_tie_score (p, tie_idx, "wrong dir");
608       else
609         penalty += p;
610     }
611
612   {
613     Real relevant_dist = max (fabs (curve_y - tie_y) - 0.5, 0.0);
614     Real p = details_.vertical_distance_penalty_factor_ * convex_amplifier (1.0, 0.9, relevant_dist);
615     if (ties_conf)
616       ties_conf->add_tie_score (p, tie_idx, "vdist");
617     else
618       penalty += p; 
619   }
620   
621   Direction d = LEFT;
622   do
623     {
624       if (!spec.note_head_drul_[d])
625         continue;
626       
627       Interval head_x = spec.note_head_drul_[d]->extent (x_refpoint_, X_AXIS);
628       Real dist = head_x.distance (conf->attachment_x_[d]);
629       
630
631       /*
632         TODO: flatten with log or sqrt.
633        */
634       Real p = details_.horizontal_distance_penalty_factor_
635         * convex_amplifier (1.25, 1.0, dist);
636       if (ties_conf)
637         ties_conf->add_tie_score (p, tie_idx,
638                                   (d == LEFT) ? "lhdist" : "rhdist");
639       else
640         penalty += p;
641
642     }
643   while (flip (&d) != LEFT);
644
645   if (ties_conf
646       && ties_conf->size () == 1)
647     {
648       Direction d = LEFT;
649       Drul_array<Grob*> stems (0, 0);
650       do
651         {
652           if (!spec.note_head_drul_[d])
653             continue;
654
655           Grob *stem = unsmob_grob (spec.note_head_drul_[d]->get_object ("stem"));
656           if (stem
657               && Stem::is_normal_stem (stem))
658             stems[d] = stem;
659         }
660       while (flip (&d) != LEFT);
661
662       bool tie_stem_dir_ok = true;
663       bool tie_position_dir_ok = true;
664       if (stems[LEFT] && !stems[RIGHT])
665         tie_stem_dir_ok = conf->dir_ != get_grob_direction (stems[LEFT]);
666       else if (!stems[LEFT] && stems[RIGHT])
667         tie_stem_dir_ok = conf->dir_ != get_grob_direction (stems[RIGHT]);
668       else if (stems[LEFT] && stems[RIGHT]
669           && get_grob_direction (stems[LEFT]) == get_grob_direction (stems[RIGHT]))
670         tie_stem_dir_ok = conf->dir_ != get_grob_direction (stems[LEFT]);
671       else if (spec.position_)
672         tie_position_dir_ok = conf->dir_ == sign (spec.position_);
673
674       if (!tie_stem_dir_ok)
675         ties_conf->add_score (details_.same_dir_as_stem_penalty_, "tie/stem dir");
676       if (!tie_position_dir_ok)
677         ties_conf->add_score (details_.same_dir_as_stem_penalty_, "tie/pos dir");
678     }
679             
680   return penalty;
681 }
682
683
684 Slice
685 Tie_formatting_problem::head_positions_slice (int rank) const
686 {
687   Position_extent_map::const_iterator i (head_positions_.find (rank));
688   if (i != head_positions_.end ())
689     {
690       return  (*i).second; 
691     }
692   Slice empty;
693   return empty;
694 }
695
696 /*
697   Score a configuration, ie. how well these ties looks without regard
698   to the note heads that they should connect to.
699  */
700 void
701 Tie_formatting_problem::score_configuration (Tie_configuration *conf) const
702 {
703   if (conf->scored_)
704     {
705       return ;
706     }
707   
708   Real length = conf->attachment_x_.length ();
709
710   Real length_penalty
711     = peak_around (0.33 * details_.min_length_, details_.min_length_, length);
712   conf->add_score (details_.min_length_penalty_factor_
713                    * length_penalty, "minlength");
714   
715   Real tip_pos = conf->position_ + conf->delta_y_ / 0.5 * details_.staff_space_;
716   Real tip_y = tip_pos * details_.staff_space_ * 0.5;
717   Real height =  conf->height (details_);
718
719   Real top_y = tip_y + conf->dir_ * height;
720   Real top_pos = 2 * top_y / details_.staff_space_;
721   Real round_top_pos = rint (top_pos);
722   if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_,
723                                                 int (round_top_pos))
724       && Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_) > top_y)
725     {
726       conf->add_score (
727         details_.staff_line_collision_penalty_
728         * peak_around (0.1 * details_.center_staff_line_clearance_,
729                        details_.center_staff_line_clearance_,
730                        fabs (top_pos - round_top_pos)),
731         "line center");
732     }
733
734   int rounded_tip_pos = int (rint (tip_pos));
735   if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, rounded_tip_pos)
736       && (head_positions_slice (conf->column_ranks_[LEFT]).contains (rounded_tip_pos)
737           || head_positions_slice (conf->column_ranks_[RIGHT]).contains (rounded_tip_pos)
738           || abs (rounded_tip_pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
739           )
740     {
741       conf->add_score (details_.staff_line_collision_penalty_
742                        * peak_around (0.1 * details_.tip_staff_line_clearance_,
743                                       details_.tip_staff_line_clearance_,
744                                       fabs (tip_pos - rint (tip_pos))),
745                        "tipline");
746     }
747
748   if (!dot_x_.is_empty ())
749     {
750       /* use left edge? */
751       Real x = dot_x_.center ();
752       
753       Bezier b = conf->get_transformed_bezier (details_);
754       if (b.control_point_extent (X_AXIS).contains (x))
755         {
756           Real y = b.get_other_coordinate (X_AXIS, x);
757
758           for (set<int>::const_iterator i (dot_positions_.begin ());
759                i != dot_positions_.end (); i ++)
760             {
761               int dot_pos = (*i);
762               conf->add_score (details_.dot_collision_penalty_
763                 * peak_around (.1 * details_.dot_collision_clearance_,
764                                details_.dot_collision_clearance_,
765                                fabs (dot_pos * details_.staff_space_ * 0.5 - y)),
766                                "dot collision");
767             }
768         }
769     }
770
771   conf->scored_ = true;
772 }
773
774 void
775 Tie_formatting_problem::score_ties_aptitude (Ties_configuration *ties) const
776 {
777   if  (ties->size () != specifications_.size ())
778     {
779       programming_error ("Huh? Mismatch between sizes.");
780       return;
781     }
782
783   for (vsize i = 0; i < ties->size (); i++)
784     score_aptitude (&ties->at (i), specifications_[i],
785                     ties, i);
786 }
787
788 void
789 Tie_formatting_problem::score_ties (Ties_configuration *ties) const
790 {
791   if (ties->scored_)
792     return;
793   
794   score_ties_configuration (ties);
795   score_ties_aptitude (ties);
796   ties->scored_ = true;
797 }
798
799 void
800 Tie_formatting_problem::score_ties_configuration (Ties_configuration *ties) const
801 {
802   for (vsize i = 0; i < ties->size (); i++)
803     {
804       score_configuration (&ties->at (i));
805       ties->add_tie_score (ties->at (i).score (), i, "conf");
806     }
807   
808   Real last_edge = 0.0;
809   Real last_center = 0.0;
810   for (vsize i = 0; i < ties->size (); i++)
811     {
812       Bezier b (ties->at (i).get_transformed_bezier (details_));
813         
814       Real center = b.curve_point (0.5)[Y_AXIS];
815       Real edge = b.curve_point (0.0)[Y_AXIS];
816       
817       if (i)
818         {
819           if (edge <= last_edge)
820             ties->add_score (details_.tie_column_monotonicity_penalty_, "monoton edge");
821           if (center <= last_center)
822             ties->add_score (details_.tie_column_monotonicity_penalty_, "monoton cent");
823
824           ties->add_score (details_.tie_tie_collision_penalty_ *
825                            peak_around (0.1 * details_.tie_tie_collision_distance_,
826                                         details_.tie_tie_collision_distance_,
827                                         fabs (center - last_center)),
828                            "tietie center");
829           ties->add_score (details_.tie_tie_collision_penalty_ *
830                            peak_around (0.1 * details_.tie_tie_collision_distance_,
831                                         details_.tie_tie_collision_distance_,
832                                         fabs (edge - last_edge)), "tietie edge");
833         }
834
835       last_edge = edge;
836       last_center = center;
837     }
838
839   if (ties->size () > 1)
840     {
841       ties->add_score (details_.outer_tie_length_symmetry_penalty_factor_
842                        * fabs (ties->at (0).attachment_x_.length () - ties->back ().attachment_x_.length ()),
843                        "length symm");
844   
845       ties->add_score (details_.outer_tie_vertical_distance_symmetry_penalty_factor_
846                        * fabs (fabs (specifications_[0].position_ * 0.5 * details_.staff_space_
847                                      - (ties->at (0).position_ * 0.5 * details_.staff_space_
848                                         + ties->at (0).delta_y_))
849                                -
850                                fabs (specifications_.back ().position_ * 0.5 * details_.staff_space_
851                                      - (ties->back ().position_ * 0.5 * details_.staff_space_
852                                         + ties->back ().delta_y_))),
853                        "pos symmetry");
854     }
855 }
856
857 /*
858   Generate with correct X-attachments and beziers, copying delta_y_
859   from TIES_CONFIG if necessary.
860 */
861 Ties_configuration
862 Tie_formatting_problem::generate_ties_configuration (Ties_configuration const &ties_config)
863 {
864   Ties_configuration copy;
865   for (vsize i = 0; i < ties_config.size (); i++)
866     {
867       Tie_configuration * ptr = get_configuration (ties_config[i].position_, ties_config[i].dir_,
868                                                    ties_config[i].column_ranks_,
869                                                    !specifications_[i].has_manual_delta_y_);
870       if (specifications_[i].has_manual_delta_y_)
871         {
872           ptr->delta_y_
873             = (specifications_[i].manual_position_ - ties_config[i].position_)
874             * 0.5 * details_.staff_space_;
875         }
876       copy.push_back (*ptr);
877     }
878   
879   return copy;
880 }
881
882 Ties_configuration
883 Tie_formatting_problem::generate_base_chord_configuration () 
884 {
885   Ties_configuration ties_config;
886   for (vsize i = 0;  i < specifications_.size (); i ++)
887     {
888       Tie_configuration conf;
889       if (specifications_[i].has_manual_dir_)
890         conf.dir_ = specifications_[i].manual_dir_;
891       if (specifications_[i].has_manual_position_)
892         {
893           conf.position_ = (int) my_round (specifications_[i].manual_position_);
894           if (specifications_[i].has_manual_delta_y_)
895             conf.delta_y_ = (specifications_[i].manual_position_ - conf.position_)
896               * 0.5 * details_.staff_space_;
897         }
898       else
899         {
900           conf.position_ = specifications_[i].position_;
901         }
902
903       conf.column_ranks_ = specifications_[i].column_ranks_;
904       ties_config.push_back (conf);
905     }
906
907   set_ties_config_standard_directions (&ties_config);
908   for (vsize i = 0; i < ties_config.size (); i++)
909     if (!specifications_[i].manual_position_)
910       ties_config[i].position_ += ties_config[i].dir_;
911
912   ties_config = generate_ties_configuration (ties_config);
913   
914   return ties_config;
915 }
916
917 Ties_configuration
918 Tie_formatting_problem::find_best_variation (Ties_configuration const &base,
919                                              vector<Tie_configuration_variation> const &vars)
920 {
921   Ties_configuration best = base;
922   
923   /*
924     This simply is 1-opt: we have K substitions, and we try applying
925     exactly every one for each.
926   */
927   for (vsize i = 0; i < vars.size (); i++)
928     {
929       Ties_configuration variant (base);
930       for (vsize j = 0; j < vars[i].index_suggestion_pairs_.size(); j++)
931         variant[vars[i].index_suggestion_pairs_[j].first] = *vars[i].index_suggestion_pairs_[j].second;
932
933       variant.reset_score ();
934       score_ties (&variant);
935       
936       if (variant.score () < best.score ())
937         {
938           best = variant;
939         }
940     }
941
942   return best;
943 }
944   
945                                        
946
947 Ties_configuration
948 Tie_formatting_problem::generate_optimal_configuration ()
949 {
950   Ties_configuration base = generate_base_chord_configuration ();
951   score_ties (&base);
952
953   vector<Tie_configuration_variation> vars;  
954   if (specifications_.size () > 1)
955     vars = generate_collision_variations (base);
956   else
957     vars = generate_single_tie_variations (base);
958
959   Ties_configuration best = find_best_variation (base, vars);
960
961   if (specifications_.size () > 1)
962     {
963       vars = generate_extremal_tie_variations (best);
964       best = find_best_variation (best, vars);
965     }
966   return best;
967 }
968
969 void
970 Tie_formatting_problem::set_ties_config_standard_directions (Ties_configuration *tie_configs)
971 {
972   if (tie_configs->empty ())
973     return ;
974
975   if (!tie_configs->at (0).dir_)
976     {
977       if (tie_configs->size () == 1)
978         tie_configs->at (0).dir_ = Direction (sign (tie_configs->at (0).position_));
979
980       if (!tie_configs->at (0).dir_)
981         tie_configs->at (0).dir_
982           = (tie_configs->size() > 1) ? DOWN : details_.neutral_direction_;
983     }
984   
985   if (!tie_configs->back ().dir_)
986     tie_configs->back ().dir_ = UP;
987
988   /*
989     Seconds
990    */
991   for (vsize i = 1; i < tie_configs->size (); i++)
992     {
993       Real diff = (tie_configs->at (i).position_
994                    -tie_configs->at (i-1).position_);
995                    
996       Real span_diff 
997         = specifications_[i].column_span () - specifications_[i-1].column_span ();
998       if (span_diff && fabs (diff) <= 2)
999         {
1000           if  (span_diff > 0)
1001             tie_configs->at (i).dir_ = UP;
1002           else if (span_diff < 0)
1003             tie_configs->at (i-1).dir_ = DOWN;  
1004         }
1005       else if (fabs (diff) <= 1)
1006         {
1007           if (!tie_configs->at (i-1).dir_)
1008             tie_configs->at (i-1).dir_ = DOWN;
1009           if (!tie_configs->at (i).dir_)
1010             tie_configs->at (i).dir_ = UP;
1011         }
1012     }
1013
1014   for (vsize i = 1; i + 1 < tie_configs->size (); i++)
1015     {
1016       Tie_configuration &conf = tie_configs->at (i);
1017       if (conf.dir_)
1018         continue;
1019
1020       Direction position_dir =
1021         Direction (sign (conf.position_));
1022       if (!position_dir)
1023         position_dir = DOWN;
1024
1025       conf.dir_ = position_dir;
1026     }
1027 }
1028
1029 vector<Tie_configuration_variation>
1030 Tie_formatting_problem::generate_extremal_tie_variations (Ties_configuration const &ties) const
1031 {
1032   vector<Tie_configuration_variation> vars;
1033   Direction d = DOWN; 
1034   for (int i = 1; i <= details_.multi_tie_region_size_; i++)
1035     {
1036       Drul_array<Tie_configuration*> configs (0, 0);
1037       do
1038         {
1039           const Tie_configuration &config = boundary (ties, d, 0);
1040           if (config.dir_ == d
1041               && !boundary (specifications_, d, 0).has_manual_position_)
1042             {
1043               Tie_configuration_variation var;
1044               configs[d] =  get_configuration (config.position_ + d * i, d,
1045                                                config.column_ranks_,
1046                                                true);
1047               var.add_suggestion((d == DOWN) ? 0 : ties.size () - 1,
1048                                  configs[d]);
1049               vars.push_back (var);
1050             }
1051         }
1052       while (flip (&d) !=  DOWN);
1053       if (configs[LEFT] && configs[RIGHT])
1054         {
1055           Tie_configuration_variation var;
1056           var.add_suggestion(0, configs[DOWN]);
1057           var.add_suggestion(ties.size() - 1, configs[UP]);
1058           vars.push_back (var);
1059         }
1060     }
1061
1062   return vars;
1063 }
1064
1065 vector<Tie_configuration_variation>
1066 Tie_formatting_problem::generate_single_tie_variations (Ties_configuration const &ties) const
1067 {
1068   vector<Tie_configuration_variation> vars;
1069
1070   int sz = details_.single_tie_region_size_;
1071   if (specifications_[0].has_manual_position_)
1072     sz = 1;
1073   for (int i = 0; i < sz; i ++)
1074     {
1075       Direction d = LEFT;
1076       do
1077         {
1078           if (i == 0
1079               && ties[0].dir_ == d)
1080             continue;
1081           
1082           int p = ties[0].position_ + i * d;
1083           
1084           if (!specifications_[0].has_manual_dir_
1085               || d == specifications_[0].manual_dir_)
1086             {
1087               Tie_configuration_variation var;
1088               var.add_suggestion(0,
1089                                  get_configuration (p,
1090                                                     d, specifications_[0].column_ranks_,
1091                                                     !specifications_[0].has_manual_delta_y_));
1092               vars.push_back (var);
1093             }
1094         }
1095       while (flip (&d) != LEFT);
1096     }
1097   return vars;
1098 }
1099
1100
1101 vector<Tie_configuration_variation>
1102 Tie_formatting_problem::generate_collision_variations (Ties_configuration const &ties) const
1103 {
1104   Real center_distance_tolerance = 0.25;
1105   
1106   vector<Tie_configuration_variation> vars;
1107   Real last_center = 0.0;
1108   for (vsize i = 0; i < ties.size (); i++)
1109     {
1110       Bezier b (ties[i].get_transformed_bezier (details_));
1111         
1112       Real center = b.curve_point (0.5)[Y_AXIS];
1113       
1114       if (i)
1115         {
1116           if (center <= last_center + center_distance_tolerance)
1117             {
1118               if (!specifications_[i].has_manual_dir_)
1119                 {
1120                   Tie_configuration_variation var;
1121                   var.add_suggestion(i,
1122                                      get_configuration (specifications_[i].position_
1123                                                        - ties[i].dir_,
1124                                                        - ties[i].dir_,
1125
1126                                                        ties[i].column_ranks_,
1127                                                        !specifications_[i].has_manual_delta_y_
1128                                                         ));
1129
1130                   vars.push_back (var);
1131                 }
1132
1133               if (!specifications_[i-1].has_manual_dir_)
1134                 {
1135                   Tie_configuration_variation var;
1136                   var.add_suggestion(i-1,
1137                                      get_configuration (specifications_[i-1].position_
1138                                                         - ties[i-1].dir_,
1139                                                         - ties[i-1].dir_,
1140                                                         specifications_[i-1].column_ranks_,
1141                                                         !specifications_[i-1].has_manual_delta_y_));
1142
1143                   vars.push_back (var);
1144                 }
1145
1146               if (i == 1 && !specifications_[i-1].has_manual_position_
1147                   && ties[i-1].dir_ == DOWN)
1148                 {
1149                   Tie_configuration_variation var;
1150                   var.add_suggestion(i-1,
1151                                      get_configuration (specifications_[i-1].position_ - 1, DOWN,
1152                                                         specifications_[i-1].column_ranks_,
1153                                                         !specifications_[i-1].has_manual_delta_y_
1154                                                         ));
1155                   vars.push_back (var);
1156                 }
1157               if (i == ties.size () && !specifications_[i].has_manual_position_
1158                   && ties[i].dir_ == UP)
1159                 {
1160                   Tie_configuration_variation var;
1161                   var.add_suggestion(i,
1162                                      get_configuration (specifications_[i].position_
1163                                                         + 1, UP,
1164                                                         specifications_[i].column_ranks_,
1165                                                         !specifications_[i].has_manual_delta_y_
1166                                                         ));
1167                   vars.push_back (var);
1168                 }
1169             }
1170           else if (dot_positions_.find (ties[i].position_) != dot_positions_.end ()
1171                    && !specifications_[i].has_manual_position_)
1172             {
1173               Tie_configuration_variation var;
1174               var.add_suggestion(i,
1175                                  get_configuration (ties[i].position_  + ties[i].dir_,
1176                                                     ties[i].dir_,
1177                                                     ties[i].column_ranks_,
1178                                                     !specifications_[i].has_manual_delta_y_
1179                                                     ));
1180               vars.push_back (var);
1181             }
1182           
1183         }
1184
1185       last_center = center;
1186     }
1187
1188
1189   return vars;
1190 }
1191
1192 void
1193 Tie_formatting_problem::set_manual_tie_configuration (SCM manual_configs)
1194 {
1195   vsize k = 0;
1196   for (SCM s = manual_configs;
1197        scm_is_pair (s) && k < specifications_.size (); s = scm_cdr (s))
1198     {
1199       SCM entry = scm_car (s);
1200       if (scm_is_pair (entry))
1201         {
1202           Tie_specification &spec = specifications_[k];
1203
1204           if (scm_is_number (scm_car (entry)))
1205             {
1206               spec.has_manual_position_ = true;
1207               spec.manual_position_ = scm_to_double (scm_car (entry));
1208               spec.has_manual_delta_y_ = (scm_inexact_p (scm_car (entry)) == SCM_BOOL_T);
1209             }
1210           
1211           if (scm_is_number (scm_cdr (entry)))
1212             {
1213               spec.has_manual_dir_ = true;
1214               spec.manual_dir_ = Direction (scm_to_int (scm_cdr (entry)));
1215             }
1216         }         
1217       k ++;
1218     }
1219 }
1220
1221
1222 void
1223 Tie_formatting_problem::set_debug_scoring (Ties_configuration const &base)
1224 {
1225 #if DEBUG_TIE_SCORING
1226   if (to_boolean (x_refpoint_->layout ()
1227                   ->lookup_variable (ly_symbol2scm ("debug-tie-scoring"))))
1228     {
1229       for (vsize i = 0; i < base.size (); i++)
1230         {
1231           string card = base.complete_tie_card (i);
1232           specifications_[i].tie_grob_->set_property ("quant-score",
1233                                                       ly_string2scm (card));
1234         }
1235     }
1236 #endif
1237 }