]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie-formatting-problem.cc
6aa5feb3b1b04e139db3829810472c3590e58581
[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   while (flip (&d) != LEFT);
680
681             
682   return penalty;
683 }
684
685
686 Slice
687 Tie_formatting_problem::head_positions_slice (int rank) const
688 {
689   Position_extent_map::const_iterator i (head_positions_.find (rank));
690   if (i != head_positions_.end ())
691     {
692       return  (*i).second; 
693     }
694   Slice empty;
695   return empty;
696 }
697
698 /*
699   Score a configuration, ie. how well these ties looks without regard
700   to the note heads that they should connect to.
701  */
702 void
703 Tie_formatting_problem::score_configuration (Tie_configuration *conf) const
704 {
705   if (conf->scored_)
706     {
707       return ;
708     }
709   
710   Real length = conf->attachment_x_.length ();
711
712   conf->add_score (details_.min_length_penalty_factor_
713                    * peak_around (0.33 * details_.min_length_, details_.min_length_, length),
714                    "minlength");
715   
716   Real tip_pos = conf->position_ + conf->delta_y_ / 0.5 * details_.staff_space_;
717   Real tip_y = tip_pos * details_.staff_space_ * 0.5;
718   Real height =  conf->height (details_);
719
720   Real top_y = tip_y + conf->dir_ * height;
721   Real top_pos = 2 * top_y / details_.staff_space_;
722   Real round_top_pos = rint (top_pos);
723   if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_,
724                                                 int (round_top_pos))
725       && Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_) > top_y)
726     {
727       conf->add_score (
728         details_.staff_line_collision_penalty_
729         * peak_around (0.1 * details_.center_staff_line_clearance_,
730                        details_.center_staff_line_clearance_,
731                        fabs (top_pos - round_top_pos)),
732         "line center");
733     }
734
735   int rounded_tip_pos = int (rint (tip_pos));
736   if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, rounded_tip_pos)
737       && (head_positions_slice (conf->column_ranks_[LEFT]).contains (rounded_tip_pos)
738           || head_positions_slice (conf->column_ranks_[RIGHT]).contains (rounded_tip_pos)
739           || abs (rounded_tip_pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
740           )
741     {
742       conf->add_score (details_.staff_line_collision_penalty_
743                        * peak_around (0.1 * details_.tip_staff_line_clearance_,
744                                       details_.tip_staff_line_clearance_,
745                                       fabs (tip_pos - rint (tip_pos))),
746                        "tipline");
747     }
748
749   if (!dot_x_.is_empty ())
750     {
751       /* use left edge? */
752       Real x = dot_x_.center ();
753       
754       Bezier b = conf->get_transformed_bezier (details_);
755       if (b.control_point_extent (X_AXIS).contains (x))
756         {
757           Real y = b.get_other_coordinate (X_AXIS, x);
758
759           for (set<int>::const_iterator i (dot_positions_.begin ());
760                i != dot_positions_.end (); i ++)
761             {
762               int dot_pos = (*i);
763               conf->add_score (details_.dot_collision_penalty_
764                 * peak_around (.1 * details_.dot_collision_clearance_,
765                                details_.dot_collision_clearance_,
766                                fabs (dot_pos * details_.staff_space_ * 0.5 - y)),
767                                "dot collision");
768             }
769         }
770     }
771
772   conf->scored_ = true;
773 }
774
775 void
776 Tie_formatting_problem::score_ties_aptitude (Ties_configuration *ties) const
777 {
778   if  (ties->size () != specifications_.size ())
779     {
780       programming_error ("Huh? Mismatch between sizes.");
781       return;
782     }
783
784   for (vsize i = 0; i < ties->size (); i++)
785     score_aptitude (&ties->at (i), specifications_[i],
786                     ties, i);
787 }
788
789 void
790 Tie_formatting_problem::score_ties (Ties_configuration *ties) const
791 {
792   if (ties->scored_)
793     return;
794   
795   score_ties_configuration (ties);
796   score_ties_aptitude (ties);
797   ties->scored_ = true;
798 }
799
800 void
801 Tie_formatting_problem::score_ties_configuration (Ties_configuration *ties) const
802 {
803   for (vsize i = 0; i < ties->size (); i++)
804     {
805       score_configuration (&ties->at (i));
806       ties->add_tie_score (ties->at (i).score (), i, "conf");
807     }
808   
809   Real last_edge = 0.0;
810   Real last_center = 0.0;
811   for (vsize i = 0; i < ties->size (); i++)
812     {
813       Bezier b (ties->at (i).get_transformed_bezier (details_));
814         
815       Real center = b.curve_point (0.5)[Y_AXIS];
816       Real edge = b.curve_point (0.0)[Y_AXIS];
817       
818       if (i)
819         {
820           if (edge <= last_edge)
821             ties->add_score (details_.tie_column_monotonicity_penalty_, "monoton edge");
822           if (center <= last_center)
823             ties->add_score (details_.tie_column_monotonicity_penalty_, "monoton cent");
824
825           ties->add_score (details_.tie_tie_collision_penalty_ *
826                            peak_around (0.1 * details_.tie_tie_collision_distance_,
827                                         details_.tie_tie_collision_distance_,
828                                         fabs (center - last_center)),
829                            "tietie center");
830           ties->add_score (details_.tie_tie_collision_penalty_ *
831                            peak_around (0.1 * details_.tie_tie_collision_distance_,
832                                         details_.tie_tie_collision_distance_,
833                                         fabs (edge - last_edge)), "tietie edge");
834         }
835
836       last_edge = edge;
837       last_center = center;
838     }
839
840   if (ties->size () > 1)
841     {
842       ties->add_score (details_.outer_tie_length_symmetry_penalty_factor_
843                        * fabs (ties->at (0).attachment_x_.length () - ties->back ().attachment_x_.length ()),
844                        "length symm");
845   
846       ties->add_score (details_.outer_tie_vertical_distance_symmetry_penalty_factor_
847                        * fabs (fabs (specifications_[0].position_ * 0.5 * details_.staff_space_
848                                      - (ties->at (0).position_ * 0.5 * details_.staff_space_
849                                         + ties->at (0).delta_y_))
850                                -
851                                fabs (specifications_.back ().position_ * 0.5 * details_.staff_space_
852                                      - (ties->back ().position_ * 0.5 * details_.staff_space_
853                                         + ties->back ().delta_y_))),
854                        "pos symmetry");
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       variant[vars[i].index_] = *vars[i].suggestion_;
931
932       variant.reset_score ();
933       score_ties (&variant);
934       
935       if (variant.score () < best.score ())
936         {
937           best = variant;
938         }
939     }
940
941   return best;
942 }
943   
944                                        
945
946 Ties_configuration
947 Tie_formatting_problem::generate_optimal_configuration ()
948 {
949   Ties_configuration base = generate_base_chord_configuration ();
950   score_ties (&base);
951
952   vector<Tie_configuration_variation> vars;  
953   if (specifications_.size () > 1)
954     vars = generate_collision_variations (base);
955   else
956     vars = generate_single_tie_variations (base);
957
958   Ties_configuration best = find_best_variation (base, vars);
959
960   if (specifications_.size () > 1)
961     {
962       vars = generate_extremal_tie_variations (best);
963       best = find_best_variation (best, vars);
964     }
965   return best;
966 }
967
968 void
969 Tie_formatting_problem::set_ties_config_standard_directions (Ties_configuration *tie_configs)
970 {
971   if (tie_configs->empty ())
972     return ;
973
974   if (!tie_configs->at (0).dir_)
975     {
976       if (tie_configs->size () == 1)
977         tie_configs->at (0).dir_ = Direction (sign (tie_configs->at (0).position_));
978
979       if (!tie_configs->at (0).dir_)
980         tie_configs->at (0).dir_
981           = (tie_configs->size() > 1) ? DOWN : details_.neutral_direction_;
982     }
983   
984   if (!tie_configs->back ().dir_)
985     tie_configs->back ().dir_ = UP;
986
987   /*
988     Seconds
989    */
990   for (vsize i = 1; i < tie_configs->size (); i++)
991     {
992       Real diff = (tie_configs->at (i).position_
993                    -tie_configs->at (i-1).position_);
994                    
995       Real span_diff 
996         = specifications_[i].column_span () - specifications_[i-1].column_span ();
997       if (span_diff && fabs (diff) <= 2)
998         {
999           if  (span_diff > 0)
1000             tie_configs->at (i).dir_ = UP;
1001           else if (span_diff < 0)
1002             tie_configs->at (i-1).dir_ = DOWN;  
1003         }
1004       else if (fabs (diff) <= 1)
1005         {
1006           if (!tie_configs->at (i-1).dir_)
1007             tie_configs->at (i-1).dir_ = DOWN;
1008           if (!tie_configs->at (i).dir_)
1009             tie_configs->at (i).dir_ = UP;
1010         }
1011     }
1012
1013   for (vsize i = 1; i + 1 < tie_configs->size (); i++)
1014     {
1015       Tie_configuration &conf = tie_configs->at (i);
1016       if (conf.dir_)
1017         continue;
1018
1019       Direction position_dir =
1020         Direction (sign (conf.position_));
1021       if (!position_dir)
1022         position_dir = DOWN;
1023
1024       conf.dir_ = position_dir;
1025     }
1026 }
1027
1028 Tie_configuration_variation::Tie_configuration_variation ()
1029 {
1030   index_ = 0;
1031   suggestion_ = 0;
1032 }
1033
1034 vector<Tie_configuration_variation>
1035 Tie_formatting_problem::generate_extremal_tie_variations (Ties_configuration const &ties) const
1036 {
1037   vector<Tie_configuration_variation> vars;
1038   Direction d = DOWN;
1039   do
1040     {
1041       if (boundary (ties, d, 0).dir_ == d
1042           && !boundary (specifications_, d, 0).has_manual_position_)
1043         for (int i = 1; i <= details_.multi_tie_region_size_; i++)
1044           {
1045             Tie_configuration_variation var;
1046             var.index_ = (d == DOWN) ? 0 : ties.size () - 1;
1047             var.suggestion_ = get_configuration (boundary (ties, d, 0).position_
1048                                                  + d * i, d,
1049                                                  boundary (ties, d, 0).column_ranks_,
1050                                                  true);
1051             vars.push_back (var);
1052           }
1053     }
1054   while (flip (&d) !=  DOWN);
1055
1056   return vars;
1057 }
1058
1059 vector<Tie_configuration_variation>
1060 Tie_formatting_problem::generate_single_tie_variations (Ties_configuration const &ties) const
1061 {
1062   vector<Tie_configuration_variation> vars;
1063
1064   int sz = details_.single_tie_region_size_;
1065   if (specifications_[0].has_manual_position_)
1066     sz = 1;
1067   for (int i = 0; i < sz; i ++)
1068     {
1069       Direction d = LEFT;
1070       do
1071         {
1072           if (i == 0
1073               && ties[0].dir_ == d)
1074             continue;
1075           
1076           int p = ties[0].position_ + i * d;
1077           
1078           if (!specifications_[0].has_manual_dir_
1079               || d == specifications_[0].manual_dir_)
1080             {
1081               Tie_configuration_variation var;
1082               var.index_ = 0;
1083               var.suggestion_ = get_configuration (p,
1084                                                    d, specifications_[0].column_ranks_,
1085                                                    !specifications_[0].has_manual_delta_y_);
1086               vars.push_back (var);
1087             }
1088         }
1089       while (flip (&d) != LEFT);
1090     }
1091   return vars;
1092 }
1093
1094
1095 vector<Tie_configuration_variation>
1096 Tie_formatting_problem::generate_collision_variations (Ties_configuration const &ties) const
1097 {
1098   Real center_distance_tolerance = 0.25;
1099   
1100   vector<Tie_configuration_variation> vars;
1101   Real last_center = 0.0;
1102   for (vsize i = 0; i < ties.size (); i++)
1103     {
1104       Bezier b (ties[i].get_transformed_bezier (details_));
1105         
1106       Real center = b.curve_point (0.5)[Y_AXIS];
1107       
1108       if (i)
1109         {
1110           if (center <= last_center + center_distance_tolerance)
1111             {
1112               if (!specifications_[i].has_manual_dir_)
1113                 {
1114                   Tie_configuration_variation var;
1115                   var.index_ = i;
1116                   var.suggestion_ = get_configuration (specifications_[i].position_
1117                                                        - ties[i].dir_,
1118                                                        - ties[i].dir_,
1119
1120                                                        ties[i].column_ranks_,
1121                                                        !specifications_[i].has_manual_delta_y_
1122                                                        );
1123
1124                   vars.push_back (var);
1125                 }
1126
1127               if (!specifications_[i-1].has_manual_dir_)
1128                 {
1129                   Tie_configuration_variation var;
1130                   var.index_ = i-1;
1131                   var.suggestion_ = get_configuration (specifications_[i-1].position_
1132                                                        - ties[i-1].dir_,
1133                                                        - ties[i-1].dir_,
1134                                                        specifications_[i-1].column_ranks_,
1135                                                        !specifications_[i-1].has_manual_delta_y_
1136                                                        );
1137
1138                   vars.push_back (var);
1139                 }
1140
1141               if (i == 1 && !specifications_[i-1].has_manual_position_
1142                   && ties[i-1].dir_ == DOWN)
1143                 {
1144                   Tie_configuration_variation var;
1145                   var.index_ = i-1;
1146                   var.suggestion_ = get_configuration (specifications_[i-1].position_ - 1, DOWN,
1147                                                        specifications_[i-1].column_ranks_,
1148                                                        !specifications_[i-1].has_manual_delta_y_
1149
1150                                                        );
1151                   vars.push_back (var);
1152                 }
1153               if (i == ties.size () && !specifications_[i].has_manual_position_
1154                   && ties[i].dir_ == UP)
1155                 {
1156                   Tie_configuration_variation var;
1157                   var.index_ = i;
1158                   var.suggestion_ = get_configuration (specifications_[i].position_
1159                                                        + 1, UP,
1160                                                        specifications_[i].column_ranks_,
1161                                                        !specifications_[i].has_manual_delta_y_
1162                                                        );
1163                   vars.push_back (var);
1164                 }
1165             }
1166           else if (dot_positions_.find (ties[i].position_) != dot_positions_.end ()
1167                    && !specifications_[i].has_manual_position_)
1168             {
1169               Tie_configuration_variation var;
1170               var.index_ = i;
1171               var.suggestion_ = get_configuration (ties[i].position_  + ties[i].dir_,
1172                                                    ties[i].dir_,
1173                                                    ties[i].column_ranks_,
1174                                                    !specifications_[i].has_manual_delta_y_
1175                                                    );
1176               vars.push_back (var);
1177             }
1178           
1179         }
1180
1181       last_center = center;
1182     }
1183
1184
1185   return vars;
1186 }
1187
1188 void
1189 Tie_formatting_problem::set_manual_tie_configuration (SCM manual_configs)
1190 {
1191   vsize k = 0;
1192   for (SCM s = manual_configs;
1193        scm_is_pair (s) && k < specifications_.size (); s = scm_cdr (s))
1194     {
1195       SCM entry = scm_car (s);
1196       if (scm_is_pair (entry))
1197         {
1198           Tie_specification &spec = specifications_[k];
1199
1200           if (scm_is_number (scm_car (entry)))
1201             {
1202               spec.has_manual_position_ = true;
1203               spec.manual_position_ = scm_to_double (scm_car (entry));
1204               spec.has_manual_delta_y_ = (scm_inexact_p (scm_car (entry)) == SCM_BOOL_T);
1205             }
1206           
1207           if (scm_is_number (scm_cdr (entry)))
1208             {
1209               spec.has_manual_dir_ = true;
1210               spec.manual_dir_ = Direction (scm_to_int (scm_cdr (entry)));
1211             }
1212         }         
1213       k ++;
1214     }
1215 }
1216
1217
1218 void
1219 Tie_formatting_problem::set_debug_scoring (Ties_configuration const &base)
1220 {
1221 #if DEBUG_TIE_SCORING
1222   if (to_boolean (x_refpoint_->layout ()
1223                   ->lookup_variable (ly_symbol2scm ("debug-tie-scoring"))))
1224     {
1225       for (vsize i = 0; i < base.size (); i++)
1226         {
1227           string card = base.complete_tie_card (i);
1228           specifications_[i].tie_grob_->set_property ("quant-score",
1229                                                       ly_string2scm (card));
1230         }
1231     }
1232 #endif
1233 }