]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie-formatting-problem.cc
* The grand 2005-2006 replace.
[lilypond.git] / lily / tie-formatting-problem.cc
1 /*
2   tie-formatting-problem.cc -- implement Tie_formatting_problem6
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2005--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
7
8 */
9
10 #include "tie-formatting-problem.hh"
11
12 #include "bezier.hh" 
13 #include "directional-element-interface.hh"
14 #include "item.hh"
15 #include "libc-extension.hh"
16 #include "note-head.hh"
17 #include "rhythmic-head.hh"
18 #include "spanner.hh" 
19 #include "staff-symbol-referencer.hh"
20 #include "stem.hh"
21 #include "tie-configuration.hh"
22 #include "tie.hh"
23 #include "warn.hh"
24
25 Interval
26 Tie_formatting_problem::get_attachment (Real y) const
27 {
28   Interval attachments;
29   Direction d = LEFT;
30   do
31     {
32       attachments[d] = skyline_height (chord_outlines_[d], y, -d);
33     }
34   while (flip (&d) != LEFT);
35   
36   return attachments;
37 }
38
39 Tie_formatting_problem::Tie_formatting_problem()
40 {
41   x_refpoint_ = 0;
42 }
43
44 Tie_formatting_problem::~Tie_formatting_problem ()
45 {
46   for (Tie_configuration_map::const_iterator i (possibilities_.begin ());
47        i != possibilities_.end (); i++)
48     delete (*i).second;
49 }
50
51 void
52 Tie_formatting_problem::set_chord_outline (Link_array<Item> bounds,
53                                            Direction d)
54 {
55   Real staff_space = Staff_symbol_referencer::staff_space (bounds[0]);
56
57   Array<Box> boxes;
58
59   Grob *stem = 0;
60   for (int i = 0; i < bounds.size (); i++)
61     {
62       Grob *head = bounds[i];
63       if (!Note_head::has_interface (head))
64         continue;
65       
66       if (!stem)
67         stem = unsmob_grob (head->get_object ("stem"));
68           
69       Real p = Staff_symbol_referencer::get_position (head);
70       Interval y ((p-1) * 0.5 * staff_space,
71                   (p+1) * 0.5 * staff_space);
72
73       Interval x = head->extent (x_refpoint_, X_AXIS);
74       boxes.push (Box (x, y));
75
76       Grob *dots = Rhythmic_head::get_dots (head);
77       if (d == LEFT && dots)
78         {
79           Interval x = dots->extent (x_refpoint_, X_AXIS);
80           Interval y (-0.5, 0.5);
81           int p = int (Staff_symbol_referencer::get_position (dots));
82           y.translate (p);
83
84           dot_positions_.insert (p);
85           dot_x_.unite (x);
86           
87           y *= staff_space * 0.5;
88           // boxes.push (Box (x, y));
89         }
90     }
91
92   chord_outlines_[d] = empty_skyline (-d);
93
94   if (bounds[0]->break_status_dir ())
95     {
96       Real x = robust_relative_extent (bounds[0],  x_refpoint_, X_AXIS)[-d];
97       chord_outlines_[d].elem_ref (0).height_ = x; 
98     }
99           
100   for (int i = 0; i < boxes.size (); i++)
101     insert_extent_into_skyline (&chord_outlines_[d]  ,
102                                 boxes[i], Y_AXIS, -d);
103
104   if (stem
105       && !Stem::is_invisible (stem))
106     {
107       Interval x;
108       x.add_point (stem->relative_coordinate (x_refpoint_, X_AXIS));
109       x.widen (staff_space / 20); // ugh.
110       Interval y;
111       y.add_point (Stem::stem_end_position (stem) * staff_space * .5);
112
113       Direction stemdir = get_grob_direction (stem);
114       y.add_point (Stem::head_positions (stem)[-stemdir]
115                    * staff_space * .5);
116           
117       insert_extent_into_skyline (&chord_outlines_[d], Box (x,y), Y_AXIS, -d);
118
119
120
121       if (d == LEFT)
122         {
123           Box flag_box = Stem::get_translated_flag (stem).extent_box ();
124           flag_box.translate( Offset (x[RIGHT], X_AXIS));
125           insert_extent_into_skyline (&chord_outlines_[d], flag_box,
126                                       Y_AXIS, -d);
127         }
128     }
129   
130   Direction updowndir = DOWN;
131   do
132     {
133       Interval x;
134       Interval y;
135       if (boxes.size())
136         {
137           Box b = boxes.boundary (updowndir, 0);
138           x = b[X_AXIS];
139           x[-d] =  b[X_AXIS].linear_combination (-d / 2);
140           y[-updowndir] = b[Y_AXIS][updowndir];
141           y[updowndir] = updowndir * infinity_f;
142         }
143
144       if (!x.is_empty ())
145         insert_extent_into_skyline (&chord_outlines_[d],
146                                     Box (x,y),
147                                     Y_AXIS, -d);
148     }
149   while (flip (&updowndir) != DOWN);
150 }
151
152
153 void
154 Tie_formatting_problem::from_tie (Grob *tie)
155 {
156   Link_array<Grob> ties;
157   ties.push (tie);
158
159   from_ties (ties);
160
161   details_.from_grob (tie);
162 }
163
164 Grob *
165 Tie_formatting_problem::common_x_refpoint () const
166 {
167   return x_refpoint_;
168 }
169
170 void
171 Tie_formatting_problem::from_ties (Link_array<Grob> const &ties)
172 {
173   if (ties.is_empty ())
174     return;
175   
176   x_refpoint_ = ties[0];
177   for (int i = 0; i < ties.size (); i++)
178     {
179       x_refpoint_ = dynamic_cast<Spanner*> (ties[i])->get_bound (LEFT)->common_refpoint (x_refpoint_, X_AXIS); 
180       x_refpoint_ = dynamic_cast<Spanner*> (ties[i])->get_bound (RIGHT)->common_refpoint (x_refpoint_, X_AXIS); 
181     }
182
183   details_.from_grob (ties[0]);
184   
185   Direction d = LEFT;
186   do
187     {
188       Link_array<Item> bounds;
189       
190       for (int i = 0; i < ties.size (); i++)
191         {
192           Item *it = dynamic_cast<Spanner*> (ties[i])->get_bound (d);
193                                              
194           bounds.push (it);
195         }
196       
197       set_chord_outline (bounds, d);
198     }
199   while (flip (&d) != LEFT);
200
201
202   for (int i = 0; i < ties.size (); i++)
203     {
204       Tie_specification spec;
205       
206       if (scm_is_number (ties[i]->get_property_data (ly_symbol2scm ("direction"))))
207         {
208           spec.manual_dir_ = to_dir (ties[i]->get_property ("direction"));
209           spec.has_manual_dir_ = true;
210         }
211           
212       spec.position_ = Tie::get_position (ties[i]);
213       
214       specifications_.push (spec);
215     }
216 }
217
218 void
219 Tie_formatting_problem::from_lv_ties (Link_array<Grob> const &lv_ties)
220 {
221   if (lv_ties.is_empty ())
222     return ;
223   
224   details_.from_grob (lv_ties[0]);
225   Link_array<Item> heads;
226   
227   for (int i = 0; i < lv_ties.size (); i++)
228     {
229       Tie_specification spec;
230       Item *head = unsmob_item (lv_ties[i]->get_object ("note-head"));
231        
232       if (!head)
233         programming_error ("LV tie without head?!");
234
235       if (head)
236         {
237           spec.position_ = int (Staff_symbol_referencer::get_position (head));
238         }
239       
240       heads.push (head);
241       specifications_.push (spec);
242     }
243
244   x_refpoint_ = lv_ties [0];
245   for (int i = 0; i < lv_ties.size (); i++)
246     {
247       x_refpoint_ = lv_ties[i]->common_refpoint (x_refpoint_, X_AXIS); 
248     }
249
250   set_chord_outline (heads, LEFT);
251
252   Real right_most = - infinity_f;   
253
254   for (int i = 0; i < chord_outlines_[LEFT].size (); i++)
255     {
256       right_most = max (right_most, chord_outlines_[LEFT][i].height_);
257     }
258
259   Skyline_entry right_entry;
260   right_entry.width_.set_full ();
261   right_entry.height_ = right_most + 1.5;
262   
263   chord_outlines_[RIGHT].push (right_entry);
264 }
265
266 Tie_configuration*
267 Tie_formatting_problem::get_configuration (int pos, Direction dir) 
268 {
269   pair<int,int> key (pos, dir);
270   Tie_configuration_map::const_iterator f = possibilities_.find (key);
271                                                               
272   if (f != possibilities_.end ())
273     {
274       return (*f).second;
275     }
276
277   
278   Tie_configuration *conf = generate_configuration (pos,dir);
279   possibilities_[key] = conf;
280   return conf;
281 }
282
283 Tie_configuration*
284 Tie_formatting_problem::generate_configuration (int pos, Direction dir) const
285 {
286   Tie_configuration *conf = new Tie_configuration;
287   conf->position_ = pos;
288   conf->dir_ = dir;
289   Real y = conf->position_ * 0.5 * details_.staff_space_;
290
291   if (dot_positions_.find (pos) != dot_positions_.end ())
292     {
293       conf->delta_y_ += 0.25 * details_.staff_space_;
294     }
295   
296   conf->attachment_x_ = get_attachment (y + conf->delta_y_);
297
298   Real h =  conf->height (details_);
299   if (!conf->delta_y_)
300     {
301       if (h < 0.5 * details_.staff_space_
302           && !Staff_symbol_referencer::on_staffline (details_.staff_symbol_referencer_, pos))
303         {
304           conf->center_tie_vertically (details_);
305         }
306       else if (h < 0.5 * details_.staff_space_
307                && Staff_symbol_referencer::on_staffline (details_.staff_symbol_referencer_, pos))
308         {
309           conf->delta_y_ += dir * 0.2 * details_.staff_space_;
310         }
311     }
312   
313   conf->attachment_x_.widen ( - details_.x_gap_);
314   return conf;
315 }
316
317 Real
318 Tie_formatting_problem::score_aptitude (Tie_configuration const &conf,
319                                         int tie_position) const
320 {
321   Real wrong_direction_offset_penalty_;
322   Real distance_penalty_factor_;
323                                   
324   wrong_direction_offset_penalty_ = 10;
325   distance_penalty_factor_ = 5;
326   
327   Real penalty = 0.0;
328   Real curve_y = conf.position_ * details_.staff_space_ * 0.5 + conf.delta_y_;
329   Real tie_y = tie_position * details_.staff_space_ * 0.5;
330   if (sign (curve_y - tie_y) != conf.dir_)
331     penalty += wrong_direction_offset_penalty_;
332
333   penalty += distance_penalty_factor_ * fabs (curve_y - tie_y);
334   return penalty;
335 }
336
337
338 Real
339 Tie_formatting_problem::score_configuration (Tie_configuration const &conf) const
340 {
341   Real length_penalty_factor = 1.0;
342   Real min_length = 0.333;
343   Real staff_line_clearance = 0.1;
344   Real staff_line_collision_penalty = 5;
345   Real dot_collision_clearance = 0.25;
346   Real dot_collision_penalty = 10;
347   
348
349   Real penalty = 0.0;
350   Real length = conf.attachment_x_.length ();
351   if (length < min_length)
352     penalty += length_penalty_factor / max (0.01, length);
353
354   Real tip_pos = conf.position_ + conf.delta_y_ / 0.5 * details_.staff_space_;
355   Real tip_y = tip_pos * details_.staff_space_ * 0.5;
356   Real height =  conf.height (details_);
357
358   Real top_y = tip_y + conf.dir_ * height;
359   Real top_pos = 2 * top_y / details_.staff_space_;
360   Real round_top_pos = rint (top_pos);
361   if (fabs (top_pos - round_top_pos) < staff_line_clearance
362       && Staff_symbol_referencer::on_staffline (details_.staff_symbol_referencer_,
363                                                 int (round_top_pos))
364       && Staff_symbol_referencer::staff_radius (details_.staff_symbol_referencer_) > top_y)
365     {
366       penalty += staff_line_collision_penalty;      
367     }
368   
369   if (fabs (tip_pos - rint (tip_pos)) < staff_line_clearance
370       && Staff_symbol_referencer::on_staffline (details_.staff_symbol_referencer_,
371                                                 int (rint (tip_pos))))
372     {
373       penalty += staff_line_collision_penalty;
374     }
375
376   if (!dot_x_.is_empty ())
377     {
378       /* use left edge? */
379       Real x = dot_x_.center ();
380       
381       Bezier b = conf.get_transformed_bezier (details_);
382       if (b.control_point_extent (X_AXIS).contains (x))
383         {
384           Real y = b.get_other_coordinate (X_AXIS, x);
385
386           for (set<int>::const_iterator i (dot_positions_.begin ());
387                i != dot_positions_.end (); i ++)
388             {
389               int dot_pos = (*i);
390               if (fabs (dot_pos * details_.staff_space_ * 0.5 - y) < dot_collision_clearance)
391                 {
392                   penalty += dot_collision_penalty;
393                 }
394             }
395         }      
396     }
397   
398   return penalty;
399 }
400
401 Tie_configuration
402 Tie_formatting_problem::find_optimal_tie_configuration (int pos, Direction dir) const
403 {
404   Link_array<Tie_configuration> confs;
405
406   int region_size = 3;
407   for (int i = 0; i < region_size; i ++)
408     {
409       confs.push (generate_configuration (pos + i * dir, dir));
410     }
411
412   Array<Real> scores;
413
414   int best_idx = -1;
415   Real best_score = 1e6;
416   for (int i = 0; i < confs.size (); i ++)
417     {
418       Real score = 0.0;
419       score += score_configuration (*confs[i]);
420       score += score_aptitude (*confs[i], pos);
421
422       if (score < best_score)
423         {
424           best_score = score;
425           best_idx = i;
426         }
427     }
428
429   Tie_configuration best = *confs[best_idx];
430   for (int i = 0; i < confs.size (); i++)
431     delete confs[i];
432
433   return best;
434 }
435
436 Tie_specification::Tie_specification ()
437 {
438   has_manual_position_ = false;
439   has_manual_dir_ = false;
440   position_ = 0;
441   manual_position_ = 0;
442   manual_dir_ = CENTER;
443 }
444
445
446 Real
447 Tie_formatting_problem::score_ties_aptitude (Ties_configuration const &ties) const
448 {
449   Real score = 0.0;
450   if  (ties.size () != specifications_.size ())
451     {
452       programming_error ("Huh? Mismatch between sizes.");
453       return infinity_f;
454     }
455
456   for (int i = 0; i < ties.size (); i++)
457     score += score_aptitude (ties[i], specifications_[i].position_);
458
459   return score;
460 }
461
462 Real
463 Tie_formatting_problem::score_ties (Ties_configuration const &ties) const
464 {
465   return score_ties_configuration (ties)
466     + score_ties_aptitude (ties);
467 }
468
469 Real
470 Tie_formatting_problem::score_ties_configuration (Ties_configuration const &ties) const
471 {
472   const Real tie_monotonicity_penalty = 100;
473   const Real tie_collision_penalty = 30;
474   const Real tie_collision_distance = 0.25;
475   
476   Real score = 0.0;
477   for (int i = 0; i < ties.size (); i++)
478     {
479       score += score_configuration (ties[i]);
480     }
481
482
483   Real last_edge = 0.0;
484   Real last_center = 0.0;
485   for (int i = 0; i < ties.size (); i++)
486     {
487       Bezier b (ties[i].get_transformed_bezier (details_));
488         
489       Real center = b.curve_point (0.5)[Y_AXIS];
490       Real edge = b.curve_point (0.0)[Y_AXIS];
491       
492       if (i)
493         {
494           if (edge <= last_edge)
495             score += tie_monotonicity_penalty;
496           if (center <= last_center)
497             score += tie_monotonicity_penalty;
498
499           score +=
500             tie_collision_penalty
501             * max (tie_collision_distance - fabs (center - last_center), 0.0) / tie_collision_distance;
502           score +=
503             tie_collision_penalty
504             * max (tie_collision_distance - fabs (edge - last_edge), 0.0) / tie_collision_distance;
505         }
506
507       last_edge = edge;
508       last_center = center;
509     }
510
511   return score;
512 }
513
514 /*
515   Generate with correct X-attachments and beziers, copying delta_y_
516   from TIES_CONFIG if necessary.
517 */
518 Ties_configuration
519 Tie_formatting_problem::generate_ties_configuration (Ties_configuration const &ties_config)
520 {
521   Ties_configuration copy;
522   for (int i = 0; i < ties_config.size (); i++)
523     {
524       Tie_configuration * ptr = get_configuration (ties_config[i].position_, ties_config[i].dir_);
525       if (specifications_[i].has_manual_position_)
526         {
527           ptr->delta_y_
528             = (specifications_[i].manual_position_ - ties_config[i].position_)
529             * 0.5 * details_.staff_space_;
530         }
531       copy.push (*ptr);
532     }
533   
534   return copy;
535 }
536
537 Ties_configuration
538 Tie_formatting_problem::generate_base_chord_configuration () 
539 {
540   Ties_configuration ties_config;
541   
542   
543   for (int i = 0;  i < specifications_.size (); i ++)
544     {
545       Tie_configuration conf;
546       if (specifications_[i].has_manual_dir_)
547         conf.dir_ = specifications_[i].manual_dir_;
548       if (specifications_[i].has_manual_position_)
549         {
550           conf.position_ = (int) my_round (specifications_[i].manual_position_);
551           conf.delta_y_ = (specifications_[i].manual_position_ - conf.position_)
552             * 0.5 * details_.staff_space_;
553         }
554       else
555         conf.position_ = specifications_[i].position_;
556       
557       ties_config.push (conf);
558     }
559
560   set_ties_config_standard_directions (&ties_config);
561
562   ties_config = generate_ties_configuration (ties_config);
563   
564   return ties_config;
565 }
566
567 Ties_configuration
568 Tie_formatting_problem::generate_optimal_chord_configuration ()
569 {
570   Ties_configuration base = generate_base_chord_configuration ();
571   Array<Tie_configuration_variation> vars = get_variations (base);
572
573   Ties_configuration best = base;
574   Real best_score = score_ties_configuration (best);
575
576   /*
577     This simply is 1-opt: we have K substitions, and we try applying
578     exactly every one for each.
579   */
580   for (int i = 0; i < vars.size (); i++)
581     {
582       Ties_configuration variant = base;
583       variant[vars[i].index_] = *vars[i].suggestion_;
584
585       Real score = (score_ties_configuration (variant)
586                     + score_ties_aptitude (variant));
587       if (score < best_score)
588         {
589           best = variant;
590           best_score = score;
591         }
592     }
593
594   return best;
595 }
596
597 void
598 Tie_formatting_problem::set_ties_config_standard_directions (Ties_configuration *tie_configs)
599 {
600   if (tie_configs->is_empty ())
601     return ;
602   
603   if (!tie_configs->elem (0).dir_)
604     tie_configs->elem_ref (0).dir_ = DOWN;
605   if (!tie_configs->top().dir_)
606     tie_configs->top().dir_ = UP;
607
608   /*
609     Seconds
610    */
611   for (int i = 1; i < tie_configs->size (); i++)
612     {
613       Real diff = (tie_configs->elem (i-1).position_
614                    - tie_configs->elem (i).position_);
615
616       if (fabs (diff) <= 1)
617         {
618           if (!tie_configs->elem (i-1).dir_)
619             tie_configs->elem_ref (i-1).dir_ = DOWN;
620           if (!tie_configs->elem (i).dir_)
621             tie_configs->elem_ref (i).dir_ = UP;
622         }
623     }
624
625   for (int i = 1; i < tie_configs->size() - 1; i++)
626     {
627       Tie_configuration &conf = tie_configs->elem_ref (i);
628       if (conf.dir_)
629         continue;
630
631       Direction position_dir =
632         Direction (sign (conf.position_));
633       if (!position_dir)
634         position_dir = DOWN;
635
636       conf.dir_ = position_dir;
637     }
638 }
639
640 Tie_configuration_variation::Tie_configuration_variation ()
641 {
642   index_ = 0;
643   suggestion_ = 0;
644 }
645
646 Array<Tie_configuration_variation>
647 Tie_formatting_problem::get_variations (Ties_configuration const &ties) 
648 {
649   Real center_distance_tolerance = 0.25;
650   
651   Array<Tie_configuration_variation> vars;
652   Real last_center = 0.0;
653   for (int i = 0; i < ties.size (); i++)
654     {
655       Bezier b (ties[i].get_transformed_bezier (details_));
656         
657       Real center = b.curve_point (0.5)[Y_AXIS];
658       
659       if (i)
660         {
661           if (center <= last_center + center_distance_tolerance)
662             {
663               if (!specifications_[i].has_manual_dir_)
664                 {
665                   Tie_configuration_variation var;
666                   var.index_ = i;
667                   var.suggestion_ = get_configuration (ties[i].position_,
668                                                        -ties[i].dir_);
669
670                   vars.push (var);
671                 }
672
673               if (!specifications_[i-1].has_manual_dir_)
674                 {
675                   Tie_configuration_variation var;
676                   var.index_ = i-1;
677                   var.suggestion_ = get_configuration (ties[i-1].position_,
678                                                        -ties[i-1].dir_);
679
680                   vars.push (var);
681                 }
682             }
683         }
684
685       last_center = center;
686     }
687
688   return vars;
689   
690 }
691
692 void
693 Tie_formatting_problem::set_manual_tie_configuration (SCM manual_configs)
694 {
695   int k = 0;
696   for (SCM s = manual_configs;
697        scm_is_pair (s) && k < specifications_.size(); s = scm_cdr (s))
698     {
699       SCM entry = scm_car (s);
700       if (!scm_is_pair (entry))
701         continue;
702
703       Tie_specification &spec = specifications_[k];
704
705       if (scm_is_number (scm_cdr (entry)))
706         {
707           spec.has_manual_dir_ = true;
708           spec.manual_dir_ = Direction (scm_to_int (scm_cdr (entry)));
709         }
710       if (scm_is_number (scm_car (entry)))
711         {
712           spec.has_manual_position_ = true;
713           spec.manual_position_ = scm_to_double (scm_car (entry));
714         }
715           
716       k ++;
717     }
718 }
719