]> git.donarmstrong.com Git - lilypond.git/blob - lily/beam-quanting.cc
Introduce Beam_scoring_problem::quant_range, for quickly filtering out
[lilypond.git] / lily / beam-quanting.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2011 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 "beam-scoring-problem.hh"
22
23 #include <queue>  
24 #include <algorithm>
25 using namespace std;
26
27 #include "align-interface.hh"
28 #include "beam.hh"
29 #include "grob.hh"
30 #include "international.hh"
31 #include "main.hh"
32 #include "output-def.hh"
33 #include "pointer-group-interface.hh"
34 #include "staff-symbol-referencer.hh"
35 #include "stem.hh"
36 #include "warn.hh"
37
38 Real
39 get_detail (SCM alist, SCM sym, Real def)
40 {
41   SCM entry = scm_assq (sym, alist);
42
43   if (scm_is_pair (entry))
44     return robust_scm2double (scm_cdr (entry), def);
45   return def;
46 }
47
48 void
49 Beam_quant_parameters::fill (Grob *him)
50 {
51   SCM details = him->get_property ("details");
52
53   SECONDARY_BEAM_DEMERIT = get_detail (details, ly_symbol2scm ("secondary-beam-demerit"), 10.0);
54   STEM_LENGTH_DEMERIT_FACTOR = get_detail (details, ly_symbol2scm ("stem-length-demerit-factor"), 5);
55   REGION_SIZE = get_detail (details, ly_symbol2scm ("region-size"), 2);
56   BEAM_EPS = get_detail (details, ly_symbol2scm ("beam-eps"), 1e-3);
57   STEM_LENGTH_LIMIT_PENALTY = get_detail (details, ly_symbol2scm ("stem-length-limit-penalty"), 5000);
58   DAMPING_DIRECTION_PENALTY = get_detail (details, ly_symbol2scm ("damping-direction-penalty"), 800);
59   HINT_DIRECTION_PENALTY = get_detail (details, ly_symbol2scm ("hint-direction-penalty"), 20);
60   MUSICAL_DIRECTION_FACTOR = get_detail (details, ly_symbol2scm ("musical-direction-factor"), 400);
61   IDEAL_SLOPE_FACTOR = get_detail (details, ly_symbol2scm ("ideal-slope-factor"), 10);
62   ROUND_TO_ZERO_SLOPE = get_detail (details, ly_symbol2scm ("round-to-zero-slope"), 0.02);
63 }
64
65 static Real
66 shrink_extra_weight (Real x, Real fac)
67 {
68   return fabs (x) * ((x < 0) ? fac : 1.0);
69 }
70
71 /****************************************************************/
72
73 Beam_configuration::Beam_configuration ()
74 {
75   y = Interval (0.0, 0.0);
76   demerits = 0.0;
77   next_scorer_todo = ORIGINAL_DISTANCE;
78 }
79
80 bool Beam_configuration::done () const
81 {
82   return next_scorer_todo >= NUM_SCORERS;
83 }
84
85 void Beam_configuration::add (Real demerit, const string &reason)
86 {
87   demerits += demerit;
88
89 #if DEBUG_BEAM_SCORING
90   if (demerit) 
91     score_card_ += to_string (" %s %.2f", reason.c_str (), demerit);
92 #endif
93 }
94   
95 Beam_configuration* Beam_configuration::new_config (Interval start,
96                                                     Interval offset)
97 {
98   Beam_configuration* qs = new Beam_configuration;
99   qs->y = Interval (int (start[LEFT]) + offset[LEFT],
100                     int (start[RIGHT]) + offset[RIGHT]);
101
102   // This orders the sequence so we try combinations closest to the
103   // the ideal offset first.
104   Real start_score = abs (offset[RIGHT]) + abs (offset[LEFT]);
105   qs->demerits = start_score / 1000.0;
106   qs->next_scorer_todo = ORIGINAL_DISTANCE + 1;
107   
108   return qs;
109 }
110
111 /****************************************************************/
112
113 /*
114   TODO:
115
116   - Make all demerits customisable
117
118   - One sensible check per demerit (what's this --hwn)
119
120   - Add demerits for quants per se, as to forbid a specific quant
121   entirely
122 */
123 int
124 best_quant_score_idx (vector<Beam_configuration*> const &configs)
125 {
126   Real best = 1e6;
127   int best_idx = -1;
128   for (vsize i = configs.size (); i--;)
129     {
130       if (configs[i]->demerits < best)
131         {
132           best = configs [i]->demerits;
133           best_idx = i;
134         }
135     }
136
137   return best_idx;
138 }
139
140 // This is a temporary hack to see how much we can gain by using a
141 // priority queue on the beams to score.
142 static int score_count = 0;
143 LY_DEFINE (ly_beam_score_count, "ly:beam-score-count", 0, 0, 0,
144            (),
145            "count number of beam scores.") {
146   return scm_from_int (score_count);
147 }
148
149 void Beam_scoring_problem::init_stems ()
150 {
151   extract_grob_set (beam, "stems", stems);
152   for (int a = 2; a--;)
153     common[a] = common_refpoint_of_array (stems, beam, Axis (a));
154
155   Drul_array<Grob *> edge_stems(Beam::first_normal_stem (beam),
156                                 Beam::last_normal_stem (beam));
157   Direction d = LEFT;
158   do
159     x_span[d] = edge_stems[d] ? edge_stems[d]->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
160   while (flip (&d) != LEFT);
161   
162   Drul_array<bool> dirs_found (0, 0);
163   for (vsize i = 0; i < stems.size (); i++)
164     {
165       Grob *s = stems[i];
166       if (!Stem::is_normal_stem (s))
167         continue;
168       
169       Stem_info si (Stem::get_stem_info (s));
170       si.scale (1 / staff_space);
171       stem_infos.push_back (si);
172       dirs_found[si.dir_] = true;
173
174       bool f = to_boolean (s->get_property ("french-beaming"))
175         && s != edge_stems[LEFT] && s != edge_stems[RIGHT];
176
177       Real y = Beam::calc_stem_y (beam, s, common, x_span[LEFT], x_span[RIGHT], CENTER, 
178                                   Interval (0, 0), f);
179       base_lengths.push_back (y / staff_space);
180       stem_xpositions.push_back (s->relative_coordinate (common[X_AXIS], X_AXIS));
181     }
182   
183   edge_dirs = Drul_array<Direction> (CENTER, CENTER);
184   if (stem_infos.size ())
185     {
186       edge_dirs = Drul_array<Direction> (stem_infos[0].dir_,
187                                          stem_infos.back().dir_);
188     }
189
190   is_xstaff = Align_interface::has_interface (common[Y_AXIS]);
191   is_knee = dirs_found[LEFT] && dirs_found[RIGHT];
192   
193   staff_radius = Staff_symbol_referencer::staff_radius (beam);
194   edge_beam_counts =  Drul_array<int>
195     (Stem::beam_multiplicity (stems[0]).length () + 1,
196      Stem::beam_multiplicity (stems.back ()).length () + 1);
197
198   // TODO - why are we dividing by staff_space?
199   beam_translation = Beam::get_beam_translation (beam) / staff_space;
200
201   d = LEFT;
202   do
203     {
204       Real stem_offset = edge_stems[d]->relative_coordinate (common[Y_AXIS], Y_AXIS)
205         - beam->relative_coordinate (common[Y_AXIS], Y_AXIS);
206       Interval heads = Stem::head_positions(edge_stems[d]) * 0.5 * staff_space;
207
208       Direction ed = edge_dirs[d];
209       heads.widen(0.5 * staff_space
210                   + (edge_beam_counts[d] - 1) * beam_translation + beam_thickness * .5);
211       quant_range[d][ed] = ed * infinity_f;
212       quant_range[d][-ed] = heads[ed] + stem_offset;
213     }
214   while (flip (&d) != LEFT);
215 }
216
217 Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array<Real> ys)
218 {
219   beam = me;
220   unquanted_y = ys;
221     
222   /*
223     Calculations are relative to a unit-scaled staff, i.e. the quants are
224     divided by the current staff_space.
225   */
226   staff_space = Staff_symbol_referencer::staff_space (me);
227   beam_thickness = Beam::get_beam_thickness (me) / staff_space;
228   line_thickness = Staff_symbol_referencer::line_thickness (me) / staff_space;
229
230   // This is the least-squares DY, corrected for concave beams.
231   musical_dy = robust_scm2double (me->get_property ("least-squares-dy"), 0);
232
233   parameters.fill (me);
234   init_stems ();
235 }
236
237 void
238 Beam_scoring_problem::generate_quants (vector<Beam_configuration*> *scores) const
239 {
240   int region_size = (int) parameters.REGION_SIZE;
241
242   /*
243     Knees are harder, lets try some more possibilities for knees.
244   */  
245   if (is_knee)
246     region_size += 2;
247   
248   Real straddle = 0.0;
249   Real sit = (beam_thickness - line_thickness) / 2;
250   Real inter = 0.5;
251   Real hang = 1.0 - (beam_thickness - line_thickness) / 2;
252   Real base_quants [] = {straddle, sit, inter, hang};
253   int num_base_quants = int (sizeof (base_quants) / sizeof (Real));
254
255   /*
256     Asymetry ? should run to <= region_size ?
257   */
258   vector<Real> unshifted_quants;
259   for (int i = -region_size; i < region_size; i++)
260     for (int j = 0; j < num_base_quants; j++)
261       {
262         unshifted_quants.push_back (i + base_quants[j]);
263       }
264
265   for (vsize i = 0; i < unshifted_quants.size (); i++)
266     for (vsize j = 0; j < unshifted_quants.size (); j++)
267       {
268         Beam_configuration *c =
269           Beam_configuration::new_config (unquanted_y,
270                                           Interval (unshifted_quants[i],
271                                                     unshifted_quants[j]));
272         
273         Direction d = LEFT;
274         do
275           {
276             if (!quant_range[d].contains (c->y[d]))
277               {
278                 delete c;
279                 c = NULL;
280                 break;
281               }
282           }
283         while (flip (&d) != LEFT);
284         if (c)   
285           scores->push_back (c);
286       }
287     
288 }
289
290
291 void Beam_scoring_problem::one_scorer (Beam_configuration* config) const
292 {
293   score_count ++;
294   switch (config->next_scorer_todo) {
295   case SLOPES:
296     score_slopes_dy (config);
297     break;
298   case FORBIDDEN:
299     score_forbidden_quants (config);
300     break;
301   case STEM_LENGTHS:
302     score_stem_lengths (config);
303     break;
304    
305   case NUM_SCORERS:
306   case ORIGINAL_DISTANCE:
307   default:
308     assert (false);
309   }
310   config->next_scorer_todo++;
311 }                                  
312
313
314 Beam_configuration *
315 Beam_scoring_problem::force_score (SCM inspect_quants, const vector<Beam_configuration*> &configs) const
316 {
317   Drul_array<Real> ins = ly_scm2interval (inspect_quants);
318   Real mindist = 1e6;
319   Beam_configuration *best = NULL; 
320   for (vsize i = 0; i < configs.size (); i++)
321     {
322       Real d = fabs (configs[i]->y[LEFT]- ins[LEFT]) + fabs (configs[i]->y[RIGHT] - ins[RIGHT]);
323       if (d < mindist)
324         {
325           best = configs[i];
326           mindist = d;
327         }
328     }
329   if (mindist > 1e5)
330     programming_error ("cannot find quant");
331
332   return best;
333 }
334
335 Drul_array<Real>
336 Beam_scoring_problem::solve () const {
337   vector<Beam_configuration*> configs;
338   generate_quants (&configs);
339
340   Beam_configuration *best = NULL;  
341
342   SCM inspect_quants = beam->get_property ("inspect-quants");
343   if (to_boolean (beam->layout ()->lookup_variable (ly_symbol2scm ("debug-beam-scoring")))
344       && scm_is_pair (inspect_quants))
345     {
346       best = force_score (inspect_quants, configs);
347     }
348   else
349     {
350       std::priority_queue<Beam_configuration*, std::vector<Beam_configuration*>,
351                           Beam_configuration_less> queue;
352       for (vsize i = 0; i < configs.size(); i++)
353         queue.push(configs[i]);
354
355
356       /*
357         TODO
358
359         It would be neat if we generated new configurations on the
360         fly, depending on the best complete score so far, eg.
361
362         if (best->done()) {
363           if (best->demerits < sqrt(queue.size())
364             break;
365           while (best->demerits > sqrt(queue.size()) {
366             generate and insert new configuration
367           }
368         }
369
370         that would allow us to do away with region_size altogether.
371       */
372       while (true) {
373         best = queue.top ();
374         if (best->done ())
375           break;
376
377         queue.pop ();
378         one_scorer (best);
379         queue.push (best);
380       }
381     }
382
383   Interval final_positions = best->y;
384
385 #if DEBUG_BEAM_SCORING
386   if (to_boolean (beam->layout ()->lookup_variable (ly_symbol2scm ("debug-beam-scoring"))))
387     {
388       // debug quanting
389       int completed = 0;
390       for (vsize i = 0; i < configs.size (); i++)
391         {
392           if (configs[i]->done ())
393             completed++;
394         }
395
396       string card = best->score_card_ + to_string (" c%d/%d", completed, configs.size());
397       beam->set_property ("quant-score", ly_string2scm (card));
398     }
399 #endif
400
401   junk_pointers (configs);
402   return final_positions;
403 }
404
405 void
406 Beam_scoring_problem::score_stem_lengths (Beam_configuration* config) const
407 {
408   Real limit_penalty = parameters.STEM_LENGTH_LIMIT_PENALTY;
409   Drul_array<Real> score (0, 0);
410   Drul_array<int> count (0, 0);
411
412   for (vsize i = 0; i < stem_xpositions.size (); i++)
413     {
414       Real x = stem_xpositions[i];
415       Real dx = x_span.delta ();
416       Real beam_y = dx
417         ? config->y[RIGHT] * (x - x_span[LEFT]) / dx + config->y[LEFT] * (x_span[RIGHT] - x) / dx
418         : (config->y[RIGHT] + config->y[LEFT]) / 2;
419       Real current_y = beam_y + base_lengths[i];
420       Real length_pen = parameters.STEM_LENGTH_DEMERIT_FACTOR;
421
422       Stem_info info = stem_infos[i];
423       Direction d = info.dir_;
424
425       score[d] += limit_penalty * max (0.0, (d * (info.shortest_y_ - current_y)));
426
427       Real ideal_diff = d * (current_y - info.ideal_y_);
428       Real ideal_score = shrink_extra_weight (ideal_diff, 1.5);
429
430       /* We introduce a power, to make the scoring strictly
431          convex. Otherwise a symmetric knee beam (up/down/up/down)
432          does not have an optimum in the middle. */
433       if (is_knee)
434         ideal_score = pow (ideal_score, 1.1);
435
436       score[d] += length_pen * ideal_score;
437       count[d]++;
438     }
439
440   /* Divide by number of stems, to make the measure scale-free. */
441   Direction d = DOWN;
442   do
443     score[d] /= max (count[d], 1);
444   while (flip (&d) != DOWN);
445
446   config->add (score[LEFT] + score[RIGHT], "L");
447 }
448
449 void
450 Beam_scoring_problem::score_slopes_dy (Beam_configuration *config) const
451 {
452   Real dy = config->y.delta ();
453   Real damped_dy = unquanted_y.delta();
454   Real dem = 0.0;
455   
456   /*
457     DAMPING_DIRECTION_PENALTY is a very harsh measure, while for
458     complex beaming patterns, horizontal is often a good choice.
459
460     TODO: find a way to incorporate the complexity of the beam in this
461     penalty.
462   */
463   if (sign (damped_dy) != sign (dy))
464     {
465       if (!dy)
466         {
467           if (fabs (damped_dy / x_span.delta ()) > parameters.ROUND_TO_ZERO_SLOPE)
468             dem += parameters.DAMPING_DIRECTION_PENALTY;
469           else
470             dem += parameters.HINT_DIRECTION_PENALTY;
471         }
472       else
473         dem += parameters.DAMPING_DIRECTION_PENALTY;
474     }
475   
476   dem += parameters.MUSICAL_DIRECTION_FACTOR
477     * max (0.0, (fabs (dy) - fabs (musical_dy)));
478
479   Real slope_penalty = parameters.IDEAL_SLOPE_FACTOR;
480
481   /* Xstaff beams tend to use extreme slopes to get short stems. We
482      put in a penalty here. */
483   if (is_xstaff)
484     slope_penalty *= 10;
485
486   /* Huh, why would a too steep beam be better than a too flat one ? */
487   dem += shrink_extra_weight (fabs (damped_dy) - fabs (dy), 1.5)
488     * slope_penalty;
489
490   config->add (dem, "S");
491 }
492
493 static Real
494 my_modf (Real x)
495 {
496   return x - floor (x);
497 }
498
499 /*
500   TODO: The fixed value SECONDARY_BEAM_DEMERIT is probably flawed:
501   because for 32nd and 64th beams the forbidden quants are relatively
502   more important than stem lengths.
503 */
504 void
505 Beam_scoring_problem::score_forbidden_quants (Beam_configuration *config) const
506 {
507   Real dy = config->y.delta ();
508
509   Real extra_demerit = parameters.SECONDARY_BEAM_DEMERIT /
510     max (edge_beam_counts[LEFT], edge_beam_counts[RIGHT]);
511
512   Direction d = LEFT;
513   Real dem = 0.0;
514   Real eps = parameters.BEAM_EPS;
515
516   do
517     {
518       for (int j = 1; j <= edge_beam_counts[d]; j++)
519         {
520           Direction stem_dir = edge_dirs[d];
521
522           /*
523             The 2.2 factor is to provide a little leniency for
524             borderline cases. If we do 2.0, then the upper outer line
525             will be in the gap of the (2, sit) quant, leading to a
526             false demerit.
527           */
528           Real gap1 = config->y[d] - stem_dir * ((j - 1) * beam_translation + beam_thickness / 2 - line_thickness / 2.2);
529           Real gap2 = config->y[d] - stem_dir * (j * beam_translation - beam_thickness / 2 + line_thickness / 2.2);
530
531           Interval gap;
532           gap.add_point (gap1);
533           gap.add_point (gap2);
534
535           for (Real k = -staff_radius;
536                k <= staff_radius + eps; k += 1.0)
537             if (gap.contains (k))
538               {
539                 Real dist = min (fabs (gap[UP] - k), fabs (gap[DOWN] - k));
540
541                 /*
542                   this parameter is tuned to grace-stem-length.ly
543                 */
544                 Real fixed_demerit = 0.4;
545
546                 dem += extra_demerit
547                   * (fixed_demerit
548                      + (1 - fixed_demerit) * (dist / gap.length ()) * 2);
549               }
550         }
551     }
552   while ((flip (&d)) != LEFT);
553
554   if (max (edge_beam_counts[LEFT], edge_beam_counts[RIGHT]) >= 2)
555     {
556       Real straddle = 0.0;
557       Real sit = (beam_thickness - line_thickness) / 2;
558       Real inter = 0.5;
559       Real hang = 1.0 - (beam_thickness - line_thickness) / 2;
560
561       Direction d = LEFT;
562       do
563         {
564           if (edge_beam_counts[d] >= 2
565               && fabs (config->y[d] - edge_dirs[d] * beam_translation) < staff_radius + inter)
566             {
567               // TODO up/down symmetry.
568               if (edge_dirs[d] == UP && dy <= eps
569                   && fabs (my_modf (config->y[d]) - sit) < eps)
570                 dem += extra_demerit;
571
572               if (edge_dirs[d] == DOWN && dy >= eps
573                   && fabs (my_modf (config->y[d]) - hang) < eps)
574                 dem += extra_demerit;
575             }
576
577           if (edge_beam_counts[d] >= 3
578               && fabs (config->y[d] - 2 * edge_dirs[d] * beam_translation) < staff_radius + inter)
579             {
580               // TODO up/down symmetry.
581               if (edge_dirs[d] == UP && dy <= eps
582                   && fabs (my_modf (config->y[d]) - straddle) < eps)
583                 dem += extra_demerit;
584
585               if (edge_dirs[d] == DOWN && dy >= eps
586                   && fabs (my_modf (config->y[d]) - straddle) < eps)
587                 dem += extra_demerit;
588             }
589         }
590       while (flip (&d) != LEFT);
591     }
592
593   config->add (dem, "F");
594 }
595