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