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