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