]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie-formatting-problem.cc
d93df73abf2ef3db0d49169833cba4b7a2b7dd76
[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       if (head_positions_slice (columns[LEFT]).contains (pos)
501           || head_positions_slice (columns[RIGHT]).contains (pos)
502           || abs (pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
503         {
504           if (h < details_.intra_space_threshold_ * 0.5 * details_.staff_space_)
505             {
506               if (!Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, pos)
507                   && abs (pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
508                 {
509                   conf->center_tie_vertically (details_);
510                 }
511               else if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, pos))
512                 {
513                   conf->delta_y_ += dir *
514                                     details_.tip_staff_line_clearance_ * 0.5 * details_.staff_space_;
515                 }
516             }
517           else
518             {
519               Real top_y = y + conf->delta_y_ + conf->dir_ * h;
520               Real top_pos = top_y / (0.5 * details_.staff_space_);
521               int round_pos = int (my_round (top_pos));
522
523               /* TODO: should use other variable? */
524               Real clearance = details_.center_staff_line_clearance_;
525               if (fabs (top_pos - round_pos) < clearance
526                   && Staff_symbol_referencer::on_staff_line (details_.staff_symbol_referencer_,
527                                                              round_pos))
528                 {
529                   Real new_y = (round_pos + clearance * conf->dir_) * 0.5 * details_.staff_space_;
530                   conf->delta_y_ = (new_y - top_y);
531                 }
532             }
533         }
534     }
535   conf->attachment_x_ = get_attachment (y + conf->delta_y_, conf->column_ranks_);
536   if (conf->height (details_) < details_.intra_space_threshold_ * 0.5 * details_.staff_space_)
537     {
538       /*
539         This is less sensible for long ties, since those are more
540         horizontal.
541       */
542       Interval close_by = get_attachment (y
543                                           + conf->delta_y_
544                                           + (dir * details_.intra_space_threshold_ * 0.25
545                                              * details_.staff_space_),
546                                           conf->column_ranks_);
547
548       conf->attachment_x_.intersect (close_by);
549     }
550
551   conf->attachment_x_.widen ( - details_.x_gap_);
552
553   if (conf->column_span_length ())
554     {
555       /*
556         avoid the stems that we attach to as well. We don't do this
557         for semities (span length = 0)
558
559         It would be better to check D against HEAD-DIRECTION if
560         applicable.
561       */
562       for (LEFT_and_RIGHT (d))
563         {
564           Real y = conf->position_ * details_.staff_space_ * 0.5 + conf->delta_y_;
565           if (get_stem_extent (conf->column_ranks_[d], d, X_AXIS).is_empty ()
566               || !get_stem_extent (conf->column_ranks_[d], d, Y_AXIS).contains (y))
567             continue;
568
569           conf->attachment_x_[d]
570             = d * min (d * conf->attachment_x_[d],
571                        d * (get_stem_extent (conf->column_ranks_[d], d, X_AXIS)[-d] - d * details_.stem_gap_));
572         }
573     }
574   return conf;
575 }
576
577 Interval
578 Tie_formatting_problem::get_head_extent (int col, Direction d, Axis a) const
579 {
580   Column_extent_map::const_iterator i = head_extents_.find (Tuple2<int> (col, int (d)));
581   if (i != head_extents_.end ())
582     return (*i).second[a];
583   else
584     return Interval ();
585 }
586
587 Interval
588 Tie_formatting_problem::get_stem_extent (int col, Direction d, Axis a) const
589 {
590   Column_extent_map::const_iterator i = stem_extents_.find (Tuple2<int> (col, int (d)));
591   if (i != stem_extents_.end ())
592     return (*i).second[a];
593   else
594     return Interval ();
595 }
596
597 /**
598    TIE_IDX and TIES_CONF are optional.
599  */
600 Real
601 Tie_formatting_problem::score_aptitude (Tie_configuration *conf,
602                                         Tie_specification const &spec,
603                                         Ties_configuration *ties_conf, int tie_idx) const
604 {
605   Real penalty = 0.0;
606   Real curve_y = conf->position_ * details_.staff_space_ * 0.5 + conf->delta_y_;
607   Real tie_y = spec.position_ * details_.staff_space_ * 0.5;
608   if (sign (curve_y - tie_y) != conf->dir_)
609     {
610       Real p = details_.wrong_direction_offset_penalty_;
611       if (ties_conf)
612         ties_conf->add_tie_score (p, tie_idx, "wrong dir");
613       else
614         penalty += p;
615     }
616
617   {
618     Real relevant_dist = max (fabs (curve_y - tie_y) - 0.5, 0.0);
619     Real p = details_.vertical_distance_penalty_factor_ * convex_amplifier (1.0, 0.9, relevant_dist);
620     if (ties_conf)
621       ties_conf->add_tie_score (p, tie_idx, "vdist");
622     else
623       penalty += p;
624   }
625
626   for (LEFT_and_RIGHT (d))
627     {
628       if (!spec.note_head_drul_[d])
629         continue;
630
631       Interval head_x = spec.note_head_drul_[d]->extent (x_refpoint_, X_AXIS);
632       Real dist = head_x.distance (conf->attachment_x_[d]);
633
634       /*
635         TODO: flatten with log or sqrt.
636        */
637       Real p = details_.horizontal_distance_penalty_factor_
638                * convex_amplifier (1.25, 1.0, dist);
639       if (ties_conf)
640         ties_conf->add_tie_score (p, tie_idx,
641                                   (d == LEFT) ? "lhdist" : "rhdist");
642       else
643         penalty += p;
644
645     }
646
647   if (ties_conf
648       && ties_conf->size () == 1)
649     {
650       Drul_array<Grob *> stems (0, 0);
651       for (LEFT_and_RIGHT (d))
652         {
653           if (!spec.note_head_drul_[d])
654             continue;
655
656           Grob *stem = unsmob_grob (spec.note_head_drul_[d]->get_object ("stem"));
657           if (stem
658               && Stem::is_normal_stem (stem))
659             stems[d] = stem;
660         }
661
662       bool tie_stem_dir_ok = true;
663       bool tie_position_dir_ok = true;
664       if (stems[LEFT] && !stems[RIGHT])
665         tie_stem_dir_ok = conf->dir_ != get_grob_direction (stems[LEFT]);
666       else if (!stems[LEFT] && stems[RIGHT])
667         tie_stem_dir_ok = conf->dir_ != get_grob_direction (stems[RIGHT]);
668       else if (stems[LEFT] && stems[RIGHT]
669                && get_grob_direction (stems[LEFT]) == get_grob_direction (stems[RIGHT]))
670         tie_stem_dir_ok = conf->dir_ != get_grob_direction (stems[LEFT]);
671       else if (spec.position_)
672         tie_position_dir_ok = conf->dir_ == sign (spec.position_);
673
674       if (!tie_stem_dir_ok)
675         ties_conf->add_score (details_.same_dir_as_stem_penalty_, "tie/stem dir");
676       if (!tie_position_dir_ok)
677         ties_conf->add_score (details_.same_dir_as_stem_penalty_, "tie/pos dir");
678     }
679
680   return penalty;
681 }
682
683 Slice
684 Tie_formatting_problem::head_positions_slice (int rank) const
685 {
686   Position_extent_map::const_iterator i (head_positions_.find (rank));
687   if (i != head_positions_.end ())
688     {
689       return (*i).second;
690     }
691   Slice empty;
692   return empty;
693 }
694
695 /*
696   Score a configuration, ie. how well these ties looks without regard
697   to the note heads that they should connect to.
698  */
699 void
700 Tie_formatting_problem::score_configuration (Tie_configuration *conf) const
701 {
702   if (conf->scored_)
703     {
704       return;
705     }
706
707   Real length = conf->attachment_x_.length ();
708
709   Real length_penalty
710     = peak_around (0.33 * details_.min_length_, details_.min_length_, length);
711   conf->add_score (details_.min_length_penalty_factor_
712                    * length_penalty, "minlength");
713
714   Real tip_pos = conf->position_ + conf->delta_y_ / 0.5 * details_.staff_space_;
715   Real tip_y = tip_pos * details_.staff_space_ * 0.5;
716   Real height = conf->height (details_);
717
718   Real top_y = tip_y + conf->dir_ * height;
719   Real top_pos = 2 * top_y / details_.staff_space_;
720   Real round_top_pos = rint (top_pos);
721   if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_,
722                                         int (round_top_pos))
723       && Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_) > top_y)
724     {
725       conf->add_score (details_.staff_line_collision_penalty_
726                        * peak_around (0.1 * details_.center_staff_line_clearance_,
727                                       details_.center_staff_line_clearance_,
728                                       fabs (top_pos - round_top_pos)),
729                        "line center");
730     }
731
732   int rounded_tip_pos = int (rint (tip_pos));
733   if (Staff_symbol_referencer::on_line (details_.staff_symbol_referencer_, rounded_tip_pos)
734       && (head_positions_slice (conf->column_ranks_[LEFT]).contains (rounded_tip_pos)
735           || head_positions_slice (conf->column_ranks_[RIGHT]).contains (rounded_tip_pos)
736           || abs (rounded_tip_pos) < 2 * Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_))
737      )
738     {
739       conf->add_score (details_.staff_line_collision_penalty_
740                        * peak_around (0.1 * details_.tip_staff_line_clearance_,
741                                       details_.tip_staff_line_clearance_,
742                                       fabs (tip_pos - rint (tip_pos))),
743                        "tipline");
744     }
745
746   if (!dot_x_.is_empty ())
747     {
748       /* use left edge? */
749       Real x = dot_x_.center ();
750
751       Bezier b = conf->get_transformed_bezier (details_);
752       if (b.control_point_extent (X_AXIS).contains (x))
753         {
754           Real y = b.get_other_coordinate (X_AXIS, x);
755
756           for (set<int>::const_iterator i (dot_positions_.begin ());
757                i != dot_positions_.end (); i++)
758             {
759               int dot_pos = (*i);
760               conf->add_score (details_.dot_collision_penalty_
761                                * peak_around (.1 * details_.dot_collision_clearance_,
762                                               details_.dot_collision_clearance_,
763                                               fabs (dot_pos * details_.staff_space_ * 0.5 - y)),
764                                "dot collision");
765             }
766         }
767     }
768
769   conf->scored_ = true;
770 }
771
772 void
773 Tie_formatting_problem::score_ties_aptitude (Ties_configuration *ties) const
774 {
775   if (ties->size () != specifications_.size ())
776     {
777       programming_error ("Huh? Mismatch between sizes.");
778       return;
779     }
780
781   for (vsize i = 0; i < ties->size (); i++)
782     score_aptitude (&ties->at (i), specifications_[i],
783                     ties, i);
784 }
785
786 void
787 Tie_formatting_problem::score_ties (Ties_configuration *ties) const
788 {
789   if (ties->scored_)
790     return;
791
792   score_ties_configuration (ties);
793   score_ties_aptitude (ties);
794   ties->scored_ = true;
795 }
796
797 void
798 Tie_formatting_problem::score_ties_configuration (Ties_configuration *ties) const
799 {
800   for (vsize i = 0; i < ties->size (); i++)
801     {
802       score_configuration (&ties->at (i));
803       ties->add_tie_score (ties->at (i).score (), i, "conf");
804     }
805
806   Real last_edge = 0.0;
807   Real last_center = 0.0;
808   for (vsize i = 0; i < ties->size (); i++)
809     {
810       Bezier b (ties->at (i).get_transformed_bezier (details_));
811
812       Real center = b.curve_point (0.5)[Y_AXIS];
813       Real edge = b.curve_point (0.0)[Y_AXIS];
814
815       if (i)
816         {
817           if (edge <= last_edge)
818             ties->add_score (details_.tie_column_monotonicity_penalty_, "monoton edge");
819           if (center <= last_center)
820             ties->add_score (details_.tie_column_monotonicity_penalty_, "monoton cent");
821
822           ties->add_score (details_.tie_tie_collision_penalty_ *
823                            peak_around (0.1 * details_.tie_tie_collision_distance_,
824                                         details_.tie_tie_collision_distance_,
825                                         fabs (center - last_center)),
826                            "tietie center");
827           ties->add_score (details_.tie_tie_collision_penalty_ *
828                            peak_around (0.1 * details_.tie_tie_collision_distance_,
829                                         details_.tie_tie_collision_distance_,
830                                         fabs (edge - last_edge)), "tietie edge");
831         }
832
833       last_edge = edge;
834       last_center = center;
835     }
836
837   if (ties->size () > 1)
838     {
839       ties->add_score (details_.outer_tie_length_symmetry_penalty_factor_
840                        * fabs (ties->at (0).attachment_x_.length () - ties->back ().attachment_x_.length ()),
841                        "length symm");
842
843       ties->add_score (details_.outer_tie_vertical_distance_symmetry_penalty_factor_
844                        * fabs (fabs (specifications_[0].position_ * 0.5 * details_.staff_space_
845                                      - (ties->at (0).position_ * 0.5 * details_.staff_space_
846                                         + ties->at (0).delta_y_))
847                                -
848                                fabs (specifications_.back ().position_ * 0.5 * details_.staff_space_
849                                      - (ties->back ().position_ * 0.5 * details_.staff_space_
850                                         + ties->back ().delta_y_))),
851                        "pos symmetry");
852     }
853 }
854
855 /*
856   Generate with correct X-attachments and beziers, copying delta_y_
857   from TIES_CONFIG if necessary.
858 */
859 Ties_configuration
860 Tie_formatting_problem::generate_ties_configuration (Ties_configuration const &ties_config)
861 {
862   Ties_configuration copy;
863   for (vsize i = 0; i < ties_config.size (); i++)
864     {
865       Tie_configuration *ptr = get_configuration (ties_config[i].position_, ties_config[i].dir_,
866                                                   ties_config[i].column_ranks_,
867                                                   !specifications_[i].has_manual_delta_y_);
868       if (specifications_[i].has_manual_delta_y_)
869         {
870           ptr->delta_y_
871             = (specifications_[i].manual_position_ - ties_config[i].position_)
872               * 0.5 * details_.staff_space_;
873         }
874       copy.push_back (*ptr);
875     }
876
877   return copy;
878 }
879
880 Ties_configuration
881 Tie_formatting_problem::generate_base_chord_configuration ()
882 {
883   Ties_configuration ties_config;
884   for (vsize i = 0; i < specifications_.size (); i++)
885     {
886       Tie_configuration conf;
887       if (specifications_[i].has_manual_dir_)
888         conf.dir_ = specifications_[i].manual_dir_;
889       if (specifications_[i].has_manual_position_)
890         {
891           conf.position_ = (int) my_round (specifications_[i].manual_position_);
892           if (specifications_[i].has_manual_delta_y_)
893             conf.delta_y_ = (specifications_[i].manual_position_ - conf.position_)
894                             * 0.5 * details_.staff_space_;
895         }
896       else
897         {
898           conf.position_ = specifications_[i].position_;
899         }
900
901       conf.column_ranks_ = specifications_[i].column_ranks_;
902       ties_config.push_back (conf);
903     }
904
905   set_ties_config_standard_directions (&ties_config);
906   for (vsize i = 0; i < ties_config.size (); i++)
907     if (!specifications_[i].manual_position_)
908       ties_config[i].position_ += ties_config[i].dir_;
909
910   ties_config = generate_ties_configuration (ties_config);
911
912   return ties_config;
913 }
914
915 Ties_configuration
916 Tie_formatting_problem::find_best_variation (Ties_configuration const &base,
917                                              vector<Tie_configuration_variation> const &vars)
918 {
919   Ties_configuration best = base;
920
921   /*
922     This simply is 1-opt: we have K substitions, and we try applying
923     exactly every one for each.
924   */
925   for (vsize i = 0; i < vars.size (); i++)
926     {
927       Ties_configuration variant (base);
928       for (vsize j = 0; j < vars[i].index_suggestion_pairs_.size (); j++)
929         variant[vars[i].index_suggestion_pairs_[j].first] = *vars[i].index_suggestion_pairs_[j].second;
930
931       variant.reset_score ();
932       score_ties (&variant);
933
934       if (variant.score () < best.score ())
935         {
936           best = variant;
937         }
938     }
939
940   return best;
941 }
942
943 Ties_configuration
944 Tie_formatting_problem::generate_optimal_configuration ()
945 {
946   Ties_configuration base = generate_base_chord_configuration ();
947   score_ties (&base);
948
949   vector<Tie_configuration_variation> vars;
950   if (specifications_.size () > 1)
951     vars = generate_collision_variations (base);
952   else
953     vars = generate_single_tie_variations (base);
954
955   Ties_configuration best = find_best_variation (base, vars);
956
957   if (specifications_.size () > 1)
958     {
959       vars = generate_extremal_tie_variations (best);
960       best = find_best_variation (best, vars);
961     }
962   return best;
963 }
964
965 void
966 Tie_formatting_problem::set_ties_config_standard_directions (Ties_configuration *tie_configs)
967 {
968   if (tie_configs->empty ())
969     return;
970
971   if (!tie_configs->at (0).dir_)
972     {
973       if (tie_configs->size () == 1)
974         tie_configs->at (0).dir_ = Direction (sign (tie_configs->at (0).position_));
975
976       if (!tie_configs->at (0).dir_)
977         tie_configs->at (0).dir_
978           = (tie_configs->size () > 1) ? DOWN : details_.neutral_direction_;
979     }
980
981   if (!tie_configs->back ().dir_)
982     tie_configs->back ().dir_ = UP;
983
984   /*
985     Seconds
986    */
987   for (vsize i = 1; i < tie_configs->size (); i++)
988     {
989       Real diff = (tie_configs->at (i).position_
990                    - tie_configs->at (i - 1).position_);
991
992       Real span_diff
993         = specifications_[i].column_span () - specifications_[i - 1].column_span ();
994       if (span_diff && fabs (diff) <= 2)
995         {
996           if (span_diff > 0)
997             tie_configs->at (i).dir_ = UP;
998           else if (span_diff < 0)
999             tie_configs->at (i - 1).dir_ = DOWN;
1000         }
1001       else if (fabs (diff) <= 1)
1002         {
1003           if (!tie_configs->at (i - 1).dir_)
1004             tie_configs->at (i - 1).dir_ = DOWN;
1005           if (!tie_configs->at (i).dir_)
1006             tie_configs->at (i).dir_ = UP;
1007         }
1008     }
1009
1010   for (vsize i = 1; i + 1 < tie_configs->size (); i++)
1011     {
1012       Tie_configuration &conf = tie_configs->at (i);
1013       if (conf.dir_)
1014         continue;
1015
1016       Direction position_dir
1017         = Direction (sign (conf.position_));
1018       if (!position_dir)
1019         position_dir = DOWN;
1020
1021       conf.dir_ = position_dir;
1022     }
1023 }
1024
1025 vector<Tie_configuration_variation>
1026 Tie_formatting_problem::generate_extremal_tie_variations (Ties_configuration const &ties) const
1027 {
1028   vector<Tie_configuration_variation> vars;
1029   for (int i = 1; i <= details_.multi_tie_region_size_; i++)
1030     {
1031       Drul_array<Tie_configuration *> configs (0, 0);
1032       for (DOWN_and_UP (d))
1033         {
1034           const Tie_configuration &config = boundary (ties, d, 0);
1035           if (config.dir_ == d
1036               && !boundary (specifications_, d, 0).has_manual_position_)
1037             {
1038               Tie_configuration_variation var;
1039               configs[d] = get_configuration (config.position_ + d * i, d,
1040                                               config.column_ranks_,
1041                                               true);
1042               var.add_suggestion ((d == DOWN) ? 0 : ties.size () - 1,
1043                                   configs[d]);
1044               vars.push_back (var);
1045             }
1046         }
1047       if (configs[LEFT] && configs[RIGHT])
1048         {
1049           Tie_configuration_variation var;
1050           var.add_suggestion (0, configs[DOWN]);
1051           var.add_suggestion (ties.size () - 1, configs[UP]);
1052           vars.push_back (var);
1053         }
1054     }
1055
1056   return vars;
1057 }
1058
1059 vector<Tie_configuration_variation>
1060 Tie_formatting_problem::generate_single_tie_variations (Ties_configuration const &ties) const
1061 {
1062   vector<Tie_configuration_variation> vars;
1063
1064   int sz = details_.single_tie_region_size_;
1065   if (specifications_[0].has_manual_position_)
1066     sz = 1;
1067   for (int i = 0; i < sz; i++)
1068     {
1069       for (LEFT_and_RIGHT (d))
1070         {
1071           if (i == 0
1072               && ties[0].dir_ == d)
1073             continue;
1074
1075           int p = ties[0].position_ + i * d;
1076
1077           if (!specifications_[0].has_manual_dir_
1078               || d == specifications_[0].manual_dir_)
1079             {
1080               Tie_configuration_variation var;
1081               var.add_suggestion (0,
1082                                   get_configuration (p,
1083                                                      d, specifications_[0].column_ranks_,
1084                                                      !specifications_[0].has_manual_delta_y_));
1085               vars.push_back (var);
1086             }
1087         }
1088     }
1089   return vars;
1090 }
1091
1092 vector<Tie_configuration_variation>
1093 Tie_formatting_problem::generate_collision_variations (Ties_configuration const &ties) const
1094 {
1095   Real center_distance_tolerance = 0.25;
1096
1097   vector<Tie_configuration_variation> vars;
1098   Real last_center = 0.0;
1099   for (vsize i = 0; i < ties.size (); i++)
1100     {
1101       Bezier b (ties[i].get_transformed_bezier (details_));
1102
1103       Real center = b.curve_point (0.5)[Y_AXIS];
1104
1105       if (i)
1106         {
1107           if (center <= last_center + center_distance_tolerance)
1108             {
1109               if (!specifications_[i].has_manual_dir_)
1110                 {
1111                   Tie_configuration_variation var;
1112                   var.add_suggestion (i,
1113                                       get_configuration (specifications_[i].position_
1114                                                          - ties[i].dir_,
1115                                                          - ties[i].dir_,
1116
1117                                                          ties[i].column_ranks_,
1118                                                          !specifications_[i].has_manual_delta_y_
1119                                                         ));
1120
1121                   vars.push_back (var);
1122                 }
1123
1124               if (!specifications_[i - 1].has_manual_dir_)
1125                 {
1126                   Tie_configuration_variation var;
1127                   var.add_suggestion (i - 1,
1128                                       get_configuration (specifications_[i - 1].position_
1129                                                          - ties[i - 1].dir_,
1130                                                          - ties[i - 1].dir_,
1131                                                          specifications_[i - 1].column_ranks_,
1132                                                          !specifications_[i - 1].has_manual_delta_y_));
1133
1134                   vars.push_back (var);
1135                 }
1136
1137               if (i == 1 && !specifications_[i - 1].has_manual_position_
1138                   && ties[i - 1].dir_ == DOWN)
1139                 {
1140                   Tie_configuration_variation var;
1141                   var.add_suggestion (i - 1,
1142                                       get_configuration (specifications_[i - 1].position_ - 1, DOWN,
1143                                                          specifications_[i - 1].column_ranks_,
1144                                                          !specifications_[i - 1].has_manual_delta_y_
1145                                                         ));
1146                   vars.push_back (var);
1147                 }
1148               if (i == ties.size () && !specifications_[i].has_manual_position_
1149                   && ties[i].dir_ == UP)
1150                 {
1151                   Tie_configuration_variation var;
1152                   var.add_suggestion (i,
1153                                       get_configuration (specifications_[i].position_
1154                                                          + 1, UP,
1155                                                          specifications_[i].column_ranks_,
1156                                                          !specifications_[i].has_manual_delta_y_
1157                                                         ));
1158                   vars.push_back (var);
1159                 }
1160             }
1161           else if (dot_positions_.find (ties[i].position_) != dot_positions_.end ()
1162                    && !specifications_[i].has_manual_position_)
1163             {
1164               Tie_configuration_variation var;
1165               var.add_suggestion (i,
1166                                   get_configuration (ties[i].position_ + ties[i].dir_,
1167                                                      ties[i].dir_,
1168                                                      ties[i].column_ranks_,
1169                                                      !specifications_[i].has_manual_delta_y_
1170                                                     ));
1171               vars.push_back (var);
1172             }
1173
1174         }
1175
1176       last_center = center;
1177     }
1178
1179   return vars;
1180 }
1181
1182 void
1183 Tie_formatting_problem::set_manual_tie_configuration (SCM manual_configs)
1184 {
1185   vsize k = 0;
1186   for (SCM s = manual_configs;
1187        scm_is_pair (s) && k < specifications_.size (); s = scm_cdr (s))
1188     {
1189       SCM entry = scm_car (s);
1190       if (scm_is_pair (entry))
1191         {
1192           Tie_specification &spec = specifications_[k];
1193
1194           if (scm_is_number (scm_car (entry)))
1195             {
1196               spec.has_manual_position_ = true;
1197               spec.manual_position_ = scm_to_double (scm_car (entry));
1198               spec.has_manual_delta_y_ = (scm_inexact_p (scm_car (entry)) == SCM_BOOL_T);
1199             }
1200
1201           if (scm_is_number (scm_cdr (entry)))
1202             {
1203               spec.has_manual_dir_ = true;
1204               spec.manual_dir_ = Direction (scm_to_int (scm_cdr (entry)));
1205             }
1206         }
1207       k++;
1208     }
1209 }
1210
1211 void
1212 Tie_formatting_problem::set_debug_scoring (Ties_configuration const &base)
1213 {
1214 #if DEBUG_TIE_SCORING
1215   if (to_boolean (x_refpoint_->layout ()
1216                   ->lookup_variable (ly_symbol2scm ("debug-tie-scoring"))))
1217     {
1218       for (vsize i = 0; i < base.size (); i++)
1219         {
1220           string card = base.complete_tie_card (i);
1221           specifications_[i].tie_grob_->set_property ("annotation",
1222                                                       ly_string2scm (card));
1223         }
1224     }
1225 #endif
1226 }