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