1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 1996--2020 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
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 "config.hh"
22
23 #include "slur-scoring.hh"
24
25 #include "axis-group-interface.hh"
26 #include "accidental-interface.hh"
27 #include "beam.hh"
28 #include "clef.hh"
29 #include "directional-element-interface.hh"
30 #include "dots.hh"
31 #include "libc-extension.hh"
32 #include "international.hh"
33 #include "main.hh"
34 #include "misc.hh"
35 #include "note-column.hh"
36 #include "note-head.hh"
37 #include "output-def.hh"
38 #include "paper-column.hh"
39 #include "pitch.hh"
40 #include "pointer-group-interface.hh"
41 #include "slur-configuration.hh"
42 #include "slur.hh"
43 #include "spanner.hh"
44 #include "staff-symbol-referencer.hh"
45 #include "staff-symbol.hh"
46 #include "stem.hh"
47 #include "warn.hh"
48
49 #include <cmath>
50 #include <memory>
51 #include <queue>
52 #include <string>
53 #include <vector>
54
55 using std::string;
56 using std::unique_ptr;
57 using std::vector;
58
59 /*
60 TODO:
61
62 - curve around flag for y coordinate
63
64 - short-cut: try a smaller region first.
65
66 - handle non-visible stems better.
67
68 - try to prune number of scoring criteria
69
70 - take encompass-objects more into account when determining
71 slur shape
72
73 - calculate encompass scoring directly after determining slur shape.
74
75 - optimize.
76 */
77 class Slur_score_state;
78
Slur_score_state()79 Slur_score_state::Slur_score_state ()
80 {
81 musical_dy_ = 0.0;
82 valid_ = false;
83 edge_has_beams_ = false;
84 has_same_beam_ = false;
85 is_broken_ = false;
86 dir_ = CENTER;
87 slur_ = 0;
88 common_[X_AXIS] = 0;
89 common_[Y_AXIS] = 0;
90 }
91
~Slur_score_state()92 Slur_score_state::~Slur_score_state ()
93 {
94 }
95
96 /*
97 If a slur is broken across a line break, the direction
98 of the post-break slur must be the same as the pre-break
99 slur.
100 */
101 Direction
slur_direction() const102 Slur_score_state::slur_direction () const
103 {
104 Grob *left_neighbor = slur_->broken_neighbor (LEFT);
105
106 if (left_neighbor && left_neighbor->is_live ())
107 return get_grob_direction (left_neighbor);
108
109 Direction dir = get_grob_direction (slur_);
110
111 if (Grob *right_neighbor = slur_->broken_neighbor (RIGHT))
112 set_grob_direction (right_neighbor, dir);
113
114 return dir;
115 }
116
117 Encompass_info
get_encompass_info(Grob * notecol) const118 Slur_score_state::get_encompass_info (Grob *notecol) const
119 {
120 Grob *stem = unsmob<Grob> (get_object (notecol, "stem"));
121 Encompass_info ei;
122
123 if (!stem)
124 {
125 programming_error ("no stem for note column");
126 ei.x_ = notecol->relative_coordinate (common_[X_AXIS], X_AXIS);
127 ei.head_ = ei.stem_ = notecol->extent (common_[Y_AXIS],
128 Y_AXIS)[dir_];
129 return ei;
130 }
131 Direction stem_dir = get_grob_direction (stem);
132
133 if (Grob *head = Note_column::first_head (notecol))
134 {
135 Interval head_ext = head->extent (common_[X_AXIS], X_AXIS);
136 // FIXME: Is there a better option than setting to 0?
137 if (head_ext.is_empty ())
138 ei.x_ = 0;
139 else
140 ei.x_ = head_ext.center ();
141 }
142 else
143 ei.x_ = notecol->extent (common_[X_AXIS], X_AXIS).center ();
144
145 Grob *h = Stem::extremal_heads (stem)[Direction (dir_)];
146 if (!h)
147 {
148 ei.head_ = ei.stem_ = notecol->extent (common_[Y_AXIS], Y_AXIS)[dir_];
149 return ei;
150 }
151
152 ei.head_ = h->extent (common_[Y_AXIS], Y_AXIS)[dir_];
153
154 if ((stem_dir == dir_)
155 && !stem->extent (stem, Y_AXIS).is_empty ())
156 {
157 ei.stem_ = stem->extent (common_[Y_AXIS], Y_AXIS)[dir_];
158 if (Grob *b = Stem::get_beam (stem))
159 ei.stem_ += stem_dir * 0.5 * Beam::get_beam_thickness (b);
160
161 Interval x = stem->extent (common_[X_AXIS], X_AXIS);
162 ei.x_ = x.is_empty ()
163 ? stem->relative_coordinate (common_[X_AXIS], X_AXIS)
164 : x.center ();
165 }
166 else
167 ei.stem_ = ei.head_;
168
169 return ei;
170 }
171
172 Drul_array<Bound_info>
get_bound_info() const173 Slur_score_state::get_bound_info () const
174 {
175 Drul_array<Bound_info> extremes;
176
177 Direction dir = dir_;
178
179 for (LEFT_and_RIGHT (d))
180 {
181 extremes[d].bound_ = slur_->get_bound (d);
182 if (has_interface<Note_column> (extremes[d].bound_))
183 {
184 extremes[d].note_column_ = extremes[d].bound_;
185 extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_);
186 extremes[d].flag_ = Note_column::get_flag (extremes[d].note_column_);
187
188 if (extremes[d].stem_)
189 {
190 extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_);
191
192 for (int a = X_AXIS; a < NO_AXES; a++)
193 {
194 Axis ax = Axis (a);
195 Interval s = extremes[d].stem_->extent (common_[ax], ax);
196 if (extremes[d].flag_)
197 s.unite (extremes[d].flag_->extent (common_[ax], ax));
198 if (s.is_empty ())
199 {
200 /*
201 do not issue warning. This happens for rests and
202 whole notes.
203 */
204 s = Interval (0, 0)
205 + extremes[d].stem_->relative_coordinate (common_[ax], ax);
206 }
207 extremes[d].stem_extent_[ax] = s;
208 }
209
210 extremes[d].slur_head_
211 = Stem::extremal_heads (extremes[d].stem_)[dir];
212 if (!extremes[d].slur_head_
213 && Note_column::has_rests (extremes[d].bound_))
214 extremes[d].slur_head_ = Note_column::get_rest (extremes[d].bound_);
215 extremes[d].staff_ = Staff_symbol_referencer
216 ::get_staff_symbol (extremes[d].stem_);
217 extremes[d].staff_space_ = Staff_symbol_referencer
218 ::staff_space (extremes[d].stem_);
219 }
220
221 }
222 else if (has_interface<Note_head> (extremes[d].bound_))
223 {
224 extremes[d].slur_head_ = extremes[d].bound_;
225 }
226 if (extremes[d].slur_head_)
227 extremes[d].slur_head_x_extent_
228 = extremes[d].slur_head_->extent (common_[X_AXIS], X_AXIS);
229 }
230
231 return extremes;
232 }
233
234 void
fill(Grob * me)235 Slur_score_state::fill (Grob *me)
236 {
237 slur_ = dynamic_cast<Spanner *> (me);
238 note_columns_
239 = internal_extract_grob_array (me, ly_symbol2scm ("note-columns"));
240
241 if (note_columns_.empty ())
242 {
243 me->suicide ();
244 return;
245 }
246
247 Slur::replace_breakable_encompass_objects (me);
248 staff_space_ = Staff_symbol_referencer::staff_space (me);
249 line_thickness_ = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
250 thickness_ = from_scm<double> (get_property (me, "thickness"), 1.0) * line_thickness_;
251
252 dir_ = slur_direction ();
253 parameters_.fill (me);
254
255 extract_grob_set (me, "note-columns", columns);
256 extract_grob_set (me, "encompass-objects", extra_objects);
257
258 Spanner *sp = dynamic_cast<Spanner *> (me);
259
260 for (int i = X_AXIS; i < NO_AXES; i++)
261 {
262 Axis a = (Axis)i;
263 common_[a] = common_refpoint_of_array (columns, me, a);
264 common_[a] = common_refpoint_of_array (extra_objects, common_[a], a);
265
266 for (LEFT_and_RIGHT (d))
267 {
268 /*
269 If bound is not in note-columns, we don't want to know about
270 its Y-position
271 */
272 if (a != Y_AXIS)
273 common_[a] = common_[a]->common_refpoint (sp->get_bound (d), a);
274 }
275 }
276
277 extremes_ = get_bound_info ();
278 is_broken_ = (!(extremes_[LEFT].note_column_ || extremes_[LEFT].slur_head_)
279 || !(extremes_[RIGHT].note_column_ || extremes_[RIGHT].slur_head_));
280
281 has_same_beam_
282 = (extremes_[LEFT].stem_ && extremes_[RIGHT].stem_
283 && Stem::get_beam (extremes_[LEFT].stem_) == Stem::get_beam (extremes_[RIGHT].stem_));
284
285 base_attachments_ = get_base_attachments ();
286
287 Drul_array<Real> end_ys
288 = get_y_attachment_range ();
289
290 extra_encompass_infos_ = get_extra_encompass_infos ();
291
292 Interval additional_ys (0.0, 0.0);
293
294 for (vsize i = 0; i < extra_encompass_infos_.size (); i++)
295 {
296 if (extra_encompass_infos_[i].extents_[X_AXIS].is_empty ())
297 continue;
298
299 Real y_place = linear_interpolate (extra_encompass_infos_[i].extents_[X_AXIS].center (),
300 base_attachments_[RIGHT][X_AXIS],
301 base_attachments_[LEFT][X_AXIS],
302 end_ys[RIGHT],
303 end_ys[LEFT]);
304 Real encompass_place = extra_encompass_infos_[i].extents_[Y_AXIS][dir_];
305 if (scm_is_eq (extra_encompass_infos_[i].type_,
306 ly_symbol2scm ("inside"))
307 && minmax (dir_, encompass_place, y_place) == encompass_place
308 && (!extra_encompass_infos_[i].grob_->internal_has_interface (ly_symbol2scm ("key-signature-interface"))
309 && !has_interface<Clef> (extra_encompass_infos_[i].grob_)
310 && !extra_encompass_infos_[i].grob_->internal_has_interface (ly_symbol2scm ("time-signature-interface"))))
311 {
312 for (LEFT_and_RIGHT (d))
313 additional_ys[d] = minmax (dir_,
314 additional_ys[d],
315 (dir_
316 * (parameters_.encompass_object_range_overshoot_
317 + (y_place - encompass_place)
318 * (normalize (extra_encompass_infos_[i].extents_[X_AXIS].center (),
319 base_attachments_[RIGHT][X_AXIS],
320 base_attachments_[LEFT][X_AXIS])
321 + (dir_ == LEFT ? 0 : -1)))));
322 }
323 }
324
325 for (LEFT_and_RIGHT (d))
326 end_ys[d] += additional_ys[d];
327
328 configurations_ = enumerate_attachments (end_ys);
329 for (vsize i = 0; i < note_columns_.size (); i++)
330 encompass_infos_.push_back (get_encompass_info (note_columns_[i]));
331
332 valid_ = true;
333
334 musical_dy_ = 0.0;
335 for (LEFT_and_RIGHT (d))
336 {
337 if (!is_broken_
338 && extremes_[d].slur_head_)
339 musical_dy_ += d
340 * extremes_[d].slur_head_->relative_coordinate (common_[Y_AXIS], Y_AXIS);
341 }
342
343 edge_has_beams_
344 = (extremes_[LEFT].stem_ && Stem::get_beam (extremes_[LEFT].stem_))
345 || (extremes_[RIGHT].stem_ && Stem::get_beam (extremes_[RIGHT].stem_));
346
347 if (is_broken_)
348 musical_dy_ = 0.0;
349 }
350
351 MAKE_SCHEME_CALLBACK (Slur, calc_control_points, 1)
352 SCM
calc_control_points(SCM smob)353 Slur::calc_control_points (SCM smob)
354 {
355 Spanner *me = unsmob<Spanner> (smob);
356
357 Slur_score_state state;
358 state.fill (me);
359
360 if (!state.valid_)
361 return SCM_EOL;
362 if (state.configurations_.empty ())
363 {
364 me->warning (_ ("no viable slur configuration found"));
365 return SCM_EOL;
366 }
367
368 state.generate_curves ();
369
370 SCM end_ys = get_property (me, "positions");
371 SCM inspect_quants = get_property (me, "inspect-quants");
372 bool debug_slurs = from_scm<bool> (me->layout ()
373 ->lookup_variable (ly_symbol2scm ("debug-slur-scoring")));
374
375 if (is_number_pair (inspect_quants))
376 {
377 debug_slurs = true;
378 end_ys = inspect_quants;
379 }
380
381 Slur_configuration *best = NULL;
382 if (is_number_pair (end_ys))
383 best = state.get_forced_configuration (from_scm<Interval> (end_ys));
384 else
385 best = state.get_best_curve ();
386
387 #if DEBUG_SLUR_SCORING
388 if (debug_slurs)
389 {
390 string total = best->card ();
391 total += to_string (" TOTAL=%.2f idx=%zu", best->score (), best->index_);
392 set_property (me, "annotation", ly_string2scm (total));
393 }
394 #endif
395
396 SCM controls = SCM_EOL;
397 for (int i = 4; i--;)
398 {
399 Offset o = best->curve_.control_[i]
400 - Offset (me->relative_coordinate (state.common_[X_AXIS], X_AXIS),
401 me->relative_coordinate (state.common_[Y_AXIS], Y_AXIS));
402 controls = scm_cons (to_scm (o), controls);
403 }
404
405 return controls;
406 }
407
408 Slur_configuration *
get_forced_configuration(Interval ys) const409 Slur_score_state::get_forced_configuration (Interval ys) const
410 {
411 Slur_configuration *best = NULL;
412 Real mindist = 1e6;
413 for (vsize i = 0; i < configurations_.size (); i++)
414 {
415 Real d = fabs (configurations_[i]->attachment_[LEFT][Y_AXIS] - ys[LEFT])
416 + fabs (configurations_[i]->attachment_[RIGHT][Y_AXIS] - ys[RIGHT]);
417 if (d < mindist)
418 {
419 best = configurations_[i].get ();
420 mindist = d;
421 }
422 }
423
424 while (!best->done ())
425 best->run_next_scorer (*this);
426
427 if (mindist > 1e5)
428 programming_error ("cannot find quant");
429
430 return best;
431 }
432
433 Slur_configuration *
get_best_curve() const434 Slur_score_state::get_best_curve () const
435 {
436 std::priority_queue < Slur_configuration *, std::vector<Slur_configuration *>,
437 Slur_configuration_less > queue;
438 for (vsize i = 0; i < configurations_.size (); i++)
439 queue.push (configurations_[i].get ());
440
441 Slur_configuration *best = NULL;
442 while (true)
443 {
444 best = queue.top ();
445 if (best->done ())
446 break;
447
448 queue.pop ();
449 best->run_next_scorer (*this);
450 queue.push (best);
451 }
452
453 return best;
454 }
455
456 Interval
breakable_bound_extent(Direction d) const457 Slur_score_state::breakable_bound_extent (Direction d) const
458 {
459 Grob *paper_col = slur_->get_bound (d)->get_column ();
460 Interval ret;
461 ret.set_empty ();
462
463 extract_grob_set (slur_, "encompass-objects", extra_encompasses);
464
465 for (vsize i = 0; i < extra_encompasses.size (); i++)
466 {
467 Item *item = dynamic_cast<Item *> (extra_encompasses[i]);
468 if (item && paper_col == item->get_column ())
469 ret.unite (robust_relative_extent (item, common_[X_AXIS], X_AXIS));
470 }
471
472 return ret;
473 }
474
475 /*
476 TODO: should analyse encompasses to determine sensible region, and
477 should limit slopes available.
478 */
479
480 Drul_array<Real>
get_y_attachment_range() const481 Slur_score_state::get_y_attachment_range () const
482 {
483 Drul_array<Real> end_ys;
484 for (LEFT_and_RIGHT (d))
485 {
486 if (extremes_[d].note_column_)
487 {
488 Interval nc_extent = extremes_[d].note_column_
489 ->extent (common_[Y_AXIS], Y_AXIS);
490 if (nc_extent.is_empty ())
491 slur_->warning (_ ("slur trying to encompass an empty note column."));
492 else
493 end_ys[d] = dir_
494 * std::max (std::max (dir_ * (base_attachments_[d][Y_AXIS]
495 + parameters_.region_size_ * dir_),
496 dir_ * (dir_ + nc_extent[dir_])),
497 dir_ * base_attachments_[-d][Y_AXIS]);
498 }
499 else if (extremes_[d].slur_head_)
500 {
501 // allow only minimal movement
502 end_ys[d] = base_attachments_[d][Y_AXIS] + 0.3 * dir_;
503 }
504 else
505 end_ys[d] = base_attachments_[d][Y_AXIS] + parameters_.region_size_ * dir_;
506 }
507
508 return end_ys;
509 }
510
511 bool
spanner_less(Spanner * s1,Spanner * s2)512 spanner_less (Spanner *s1, Spanner *s2)
513 {
514 Slice b1, b2;
515 for (LEFT_and_RIGHT (d))
516 {
517 b1[d] = s1->get_bound (d)->get_column ()->get_rank ();
518 b2[d] = s2->get_bound (d)->get_column ()->get_rank ();
519 }
520
521 return b2[LEFT] <= b1[LEFT] && b2[RIGHT] >= b1[RIGHT]
522 && (b2[LEFT] != b1[LEFT] || b2[RIGHT] != b1[RIGHT]);
523 }
524
525 Drul_array<Offset>
get_base_attachments() const526 Slur_score_state::get_base_attachments () const
527 {
528 Drul_array<Offset> base_attachment;
529 for (LEFT_and_RIGHT (d))
530 {
531 Grob *stem = extremes_[d].stem_;
532 Grob *head = extremes_[d].slur_head_;
533
534 Real x = 0.0;
535 Real y = 0.0;
536 if (extremes_[d].note_column_)
537 {
538
539 /*
540 fixme: X coord should also be set in this case.
541 */
542 if (stem
543 && !Stem::is_invisible (stem)
544 && extremes_[d].stem_dir_ == dir_
545 && Stem::get_beaming (stem, -d)
546 && Stem::get_beam (stem)
547 && (!spanner_less (slur_, Stem::get_beam (stem))
548 || has_same_beam_))
549 y = extremes_[d].stem_extent_[Y_AXIS][dir_];
550 else if (head)
551 y = head->extent (common_[Y_AXIS], Y_AXIS)[dir_];
552 y += dir_ * 0.5 * staff_space_;
553
554 y = move_away_from_staffline (y, head);
555
556 Grob *fh = Note_column::first_head (extremes_[d].note_column_);
557 x
558 = (fh ? fh->extent (common_[X_AXIS], X_AXIS)
559 : extremes_[d].bound_->extent (common_[X_AXIS], X_AXIS))
560 .linear_combination (CENTER);
561 if (!std::isfinite (x))
562 x = extremes_[d].note_column_->extent (common_[X_AXIS], X_AXIS)
563 .linear_combination (CENTER);
564 if (!std::isfinite (y))
565 y = extremes_[d].note_column_->extent (common_[Y_AXIS], Y_AXIS)
566 .linear_combination (CENTER);
567 }
568 else if (head)
569 {
570 y = head->extent (common_[Y_AXIS], Y_AXIS)
571 .linear_combination (0.5 * dir_);
572
573 // Don't "move_away_from_staffline" because that makes it
574 // harder to recognize the specific attachment point
575 x = head->extent (common_[X_AXIS], X_AXIS)[-d];
576 }
577
578 base_attachment[d] = Offset (x, y);
579 }
580
581 for (LEFT_and_RIGHT (d))
582 {
583 if (!extremes_[d].note_column_ && !extremes_[d].slur_head_)
584 {
585 Real x = 0;
586 Real y = 0;
587
588 Interval ext = breakable_bound_extent (d);
589 if (ext.is_empty ())
590 ext = Axis_group_interface::
591 generic_bound_extent (extremes_[d].bound_,
592 common_[X_AXIS], X_AXIS);
593 x = ext[-d];
594
595 Grob *col = (d == LEFT) ? note_columns_[0] : note_columns_.back ();
596
597 if (extremes_[-d].bound_ != col)
598 {
599 y = robust_relative_extent (col, common_[Y_AXIS], Y_AXIS)[dir_];
600 y += dir_ * 0.5 * staff_space_;
601
602 if (get_grob_direction (col) == dir_
603 && Note_column::get_stem (col)
604 && !Stem::is_invisible (Note_column::get_stem (col)))
605 y -= dir_ * 1.5 * staff_space_;
606 }
607 else
608 y = base_attachment[-d][Y_AXIS];
609
610 y = move_away_from_staffline (y, col);
611
612 base_attachment[d] = Offset (x, y);
613 }
614 }
615
616 for (LEFT_and_RIGHT (d))
617 {
618 for (int a = X_AXIS; a < NO_AXES; a++)
619 {
620 Real &b = base_attachment[d][Axis (a)];
621
622 if (!std::isfinite (b))
623 {
624 programming_error ("slur attachment is inf/nan");
625 b = 0.0;
626 }
627 }
628 }
629
630 return base_attachment;
631 }
632
633 Real
move_away_from_staffline(Real y,Grob * on_staff) const634 Slur_score_state::move_away_from_staffline (Real y,
635 Grob *on_staff) const
636 {
637 if (!on_staff)
638 return y;
639
640 Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (on_staff);
641 if (!staff_symbol)
642 return y;
643
644 Real pos
645 = (y - staff_symbol->relative_coordinate (common_[Y_AXIS],
646 Y_AXIS))
647 * 2.0 / staff_space_;
648
649 if (fabs (pos - round_halfway_up (pos)) < 0.2
650 && Staff_symbol_referencer::on_staff_line (on_staff, (int) rint (pos)))
651 y += 1.5 * staff_space_ * dir_ / 10;
652
653 return y;
654 }
655
656 vector<Offset>
generate_avoid_offsets() const657 Slur_score_state::generate_avoid_offsets () const
658 {
659 vector<Offset> avoid;
660 vector<Grob *> encompasses = note_columns_;
661
662 for (vsize i = 0; i < encompasses.size (); i++)
663 {
664 if (extremes_[LEFT].note_column_ == encompasses[i]
665 || extremes_[RIGHT].note_column_ == encompasses[i])
666 continue;
667
668 Encompass_info inf (get_encompass_info (encompasses[i]));
669 Real y = dir_ * (std::max (dir_ * inf.head_, dir_ * inf.stem_));
670
671 avoid.push_back (Offset (inf.x_, y + dir_ * parameters_.free_head_distance_));
672 }
673
674 extract_grob_set (slur_, "encompass-objects", extra_encompasses);
675 for (vsize i = 0; i < extra_encompasses.size (); i++)
676 {
677 if (has_interface<Slur> (extra_encompasses[i]))
678 {
679 Grob *small_slur = extra_encompasses[i];
680 Bezier b = Slur::get_curve (small_slur);
681
682 Offset z = b.curve_point (0.5);
683 z += Offset (small_slur->relative_coordinate (common_[X_AXIS], X_AXIS),
684 small_slur->relative_coordinate (common_[Y_AXIS], Y_AXIS));
685
686 z[Y_AXIS] += dir_ * parameters_.free_slur_distance_;
687 avoid.push_back (z);
688 }
689 else if (scm_is_eq (get_property (extra_encompasses[i], "avoid-slur"),
690 ly_symbol2scm ("inside")))
691 {
692 Grob *g = extra_encompasses [i];
693 Interval xe = g->extent (common_[X_AXIS], X_AXIS);
694 Interval ye = g->extent (common_[Y_AXIS], Y_AXIS);
695
696 if (!xe.is_empty ()
697 && !ye.is_empty ())
698 avoid.push_back (Offset (xe.center (), ye[dir_]));
699 }
700 }
701 return avoid;
702 }
703
704 void
generate_curves() const705 Slur_score_state::generate_curves () const
706 {
707 Real r_0 = from_scm<double> (get_property (slur_, "ratio"), 0.33);
708 Real h_inf = staff_space_ * scm_to_double (get_property (slur_, "height-limit"));
709
710 vector<Offset> avoid = generate_avoid_offsets ();
711 for (vsize i = 0; i < configurations_.size (); i++)
712 configurations_[i]->generate_curve (*this, r_0, h_inf, avoid);
713 }
714
715 vector<unique_ptr<Slur_configuration>>
enumerate_attachments(Drul_array<Real> end_ys) const716 Slur_score_state::enumerate_attachments (Drul_array<Real> end_ys) const
717 {
718 vector<unique_ptr<Slur_configuration>> scores;
719
720 Drul_array<Offset> os;
721 os[LEFT] = base_attachments_[LEFT];
722 Real minimum_length = staff_space_
723 * from_scm<double> (get_property (slur_, "minimum-length"), 2.0);
724
725 for (int i = 0; dir_ * os[LEFT][Y_AXIS] <= dir_ * end_ys[LEFT]; i++)
726 {
727 os[RIGHT] = base_attachments_[RIGHT];
728 for (int j = 0; dir_ * os[RIGHT][Y_AXIS] <= dir_ * end_ys[RIGHT]; j++)
729 {
730
731 Drul_array<bool> attach_to_stem (false, false);
732 for (LEFT_and_RIGHT (d))
733 {
734 os[d][X_AXIS] = base_attachments_[d][X_AXIS];
735 if (extremes_[d].stem_
736 && !Stem::is_invisible (extremes_[d].stem_)
737 && extremes_[d].stem_dir_ == dir_)
738 {
739 Interval stem_y = extremes_[d].stem_extent_[Y_AXIS];
740 stem_y.widen (0.25 * staff_space_);
741 if (stem_y.contains (os[d][Y_AXIS]))
742 {
743 os[d][X_AXIS] = extremes_[d].stem_extent_[X_AXIS][-d]
744 - d * 0.3;
745 attach_to_stem[d] = true;
746 }
747 else if (dir_ * extremes_[d].stem_extent_[Y_AXIS][dir_]
748 < dir_ * os[d][Y_AXIS]
749 && !extremes_[d].stem_extent_[X_AXIS].is_empty ())
750
751 os[d][X_AXIS] = extremes_[d].stem_extent_[X_AXIS].center ();
752 }
753 }
754
755 Offset dz;
756 dz = os[RIGHT] - os[LEFT];
757 if (dz[X_AXIS] < minimum_length
758 || fabs (dz[Y_AXIS] / dz[X_AXIS]) > parameters_.max_slope_)
759 {
760 for (LEFT_and_RIGHT (d))
761 {
762 if (extremes_[d].slur_head_
763 && !extremes_[d].slur_head_x_extent_.is_empty ())
764 {
765 os[d][X_AXIS] = extremes_[d].slur_head_x_extent_.center ();
766 attach_to_stem[d] = false;
767 }
768 }
769 }
770
771 dz = (os[RIGHT] - os[LEFT]).direction ();
772 for (LEFT_and_RIGHT (d))
773 {
774 if (extremes_[d].slur_head_
775 && !attach_to_stem[d])
776 {
777 /* Horizontally move tilted slurs a little. Move
778 more for bigger tilts.
779
780 TODO: parameter */
781 os[d][X_AXIS]
782 -= dir_ * extremes_[d].slur_head_x_extent_.length ()
783 * dz[Y_AXIS] / 3;
784 }
785 }
786
787 scores.push_back (Slur_configuration::new_config (os, scores.size ()));
788
789 os[RIGHT][Y_AXIS] += dir_ * staff_space_ / 2;
790 }
791
792 os[LEFT][Y_AXIS] += dir_ * staff_space_ / 2;
793 }
794
795 return scores;
796 }
797
798 vector<Extra_collision_info>
get_extra_encompass_infos() const799 Slur_score_state::get_extra_encompass_infos () const
800 {
801 extract_grob_set (slur_, "encompass-objects", encompasses);
802 vector<Extra_collision_info> collision_infos;
803 for (vsize i = encompasses.size (); i--;)
804 {
805 if (has_interface<Slur> (encompasses[i]))
806 {
807 Spanner *small_slur = dynamic_cast<Spanner *> (encompasses[i]);
808 Bezier b = Slur::get_curve (small_slur);
809
810 Offset relative (small_slur->relative_coordinate (common_[X_AXIS], X_AXIS),
811 small_slur->relative_coordinate (common_[Y_AXIS], Y_AXIS));
812
813 for (int k = 0; k < 3; k++)
814 {
815 Direction hdir = Direction (k - 1);
816
817 /*
818 Only take bound into account if small slur starts
819 together with big slur.
820 */
821 if (hdir && small_slur->get_bound (hdir) != slur_->get_bound (hdir))
822 continue;
823
824 Offset z = b.curve_point (k / 2.0);
825 z += relative;
826
827 Interval yext;
828 yext.set_full ();
829 yext[dir_] = z[Y_AXIS] + dir_ * thickness_ * 1.0;
830
831 Interval xext (-1, 1);
832 xext = xext * (thickness_ * 2) + z[X_AXIS];
833 Extra_collision_info info (small_slur,
834 hdir,
835 xext,
836 yext,
837 parameters_.extra_object_collision_penalty_);
838 collision_infos.push_back (info);
839 }
840 }
841 else
842 {
843 Grob *g = encompasses [i];
844 Interval xe = g->extent (common_[X_AXIS], X_AXIS);
845 Interval ye = g->extent (common_[Y_AXIS], Y_AXIS);
846 if (has_interface<Dots> (g))
847 ye.widen (0.2);
848
849 Real xp = 0.0;
850 Real penalty = parameters_.extra_object_collision_penalty_;
851 if (has_interface<Accidental_interface> (g))
852 {
853 penalty = parameters_.accidental_collision_;
854
855 Rational alt = from_scm<Rational> (get_property (g, "alteration"));
856 SCM scm_style = get_property (g, "style");
857 if (!scm_is_symbol (scm_style)
858 && !from_scm<bool> (get_property (g, "parenthesized"))
859 && !from_scm<bool> (get_property (g, "restore-first")))
860 {
861 if (alt == FLAT_ALTERATION
862 || alt == DOUBLE_FLAT_ALTERATION)
863 xp = LEFT;
864 else if (alt == SHARP_ALTERATION)
865 xp = 0.5 * dir_;
866 else if (alt == NATURAL_ALTERATION)
867 xp = -dir_;
868 }
869 }
870
871 ye.widen (thickness_ * 0.5);
872 xe.widen (thickness_ * 1.0);
873 Extra_collision_info info (g, xp, xe, ye, penalty);
874 collision_infos.push_back (info);
875 }
876 }
877
878 return collision_infos;
879 }
880
Extra_collision_info(Grob * g,Real idx,Interval x,Interval y,Real p)881 Extra_collision_info::Extra_collision_info (Grob *g, Real idx, Interval x, Interval y, Real p)
882 {
883 idx_ = idx;
884 extents_[X_AXIS] = x;
885 extents_[Y_AXIS] = y;
886 penalty_ = p;
887 grob_ = g;
888 type_ = get_property (g, "avoid-slur");
889 }
890
Extra_collision_info()891 Extra_collision_info::Extra_collision_info ()
892 {
893 idx_ = 0.0;
894 penalty_ = 0.;
895 grob_ = 0;
896 type_ = SCM_EOL;
897 }
898