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