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