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