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