2 beam-quanting.cc -- implement Beam quanting functions
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
16 #include "staff-symbol-referencer.hh"
18 #include "output-def.hh"
19 #include "pointer-group-interface.hh"
20 #include "align-interface.hh"
23 get_detail (SCM alist, SCM sym, Real def)
25 SCM entry = scm_assq (sym, alist);
27 if (scm_is_pair (entry))
29 return robust_scm2double (scm_cdr (entry), def);
35 Beam_quant_parameters::fill (Grob *him)
37 SCM details = him->get_property ("details");
39 INTER_QUANT_PENALTY = get_detail (details, ly_symbol2scm ("inter-quant-penalty"), 1000.0);
40 SECONDARY_BEAM_DEMERIT = get_detail (details, ly_symbol2scm ("secondary-beam-demerit"), 10.0);
41 STEM_LENGTH_DEMERIT_FACTOR = get_detail (details, ly_symbol2scm ("stem-length-demerit-factor"), 5);
42 REGION_SIZE = get_detail (details, ly_symbol2scm ("region-size"), 2);
43 BEAM_EPS = get_detail (details, ly_symbol2scm ("beam-eps"), 1e-3);
45 // possibly ridiculous, but too short stems just won't do
46 STEM_LENGTH_LIMIT_PENALTY = get_detail (details, ly_symbol2scm ("stem-length-limit-penalty"), 5000);
47 DAMPING_DIRECTION_PENALTY = get_detail (details, ly_symbol2scm ("damping-direction-penalty"), 800);
48 MUSICAL_DIRECTION_FACTOR = get_detail (details, ly_symbol2scm ("musical-direction-factor"), 400);
49 IDEAL_SLOPE_FACTOR = get_detail (details, ly_symbol2scm ("ideal-slope-factor"), 10);
50 ROUND_TO_ZERO_SLOPE = get_detail (details, ly_symbol2scm ("round-to-zero-slope"), 0.02);
54 shrink_extra_weight (Real x, Real fac)
56 return fabs (x) * ((x < 0) ? fac : 1.0);
73 - Make all demerits customisable
75 - One sensible check per demerit (what's this --hwn)
77 - Add demerits for quants per se, as to forbid a specific quant
82 best_quant_score_idx (Array<Quant_score> const &qscores)
86 for (int i = qscores.size (); i--;)
88 if (qscores[i].demerits < best)
90 best = qscores [i].demerits;
98 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
100 Beam::quanting (SCM smob)
102 Grob *me = unsmob_grob (smob);
104 Beam_quant_parameters parameters;
105 parameters.fill (me);
107 SCM s = me->get_property ("positions");
108 Real yl = scm_to_double (scm_car (s));
109 Real yr = scm_to_double (scm_cdr (s));
112 Calculations are relative to a unit-scaled staff, i.e. the quants are
113 divided by the current staff_space.
116 Real ss = Staff_symbol_referencer::staff_space (me);
117 Real thickness = Beam::get_thickness (me) / ss;
118 Real slt = Staff_symbol_referencer::line_thickness (me) / ss;
120 Real dy_mus = robust_scm2double (me->get_property ("least-squares-dy"), 0);
122 Real sit = (thickness - slt) / 2;
124 Real hang = 1.0 - (thickness - slt) / 2;
125 Real quants [] = {straddle, sit, inter, hang };
127 int num_quants = int (sizeof (quants) / sizeof (Real));
132 going to REGION_SIZE == 2, yields another 0.6 second with
136 (result indexes between 70 and 575) ? --hwn.
141 Do stem computations. These depend on YL and YR linearly, so we can
142 precompute for every stem 2 factors.
144 Link_array<Grob> stems
145 = extract_grob_array (me, "stems");
146 Array<Stem_info> stem_infos;
147 Array<Real> base_lengths;
148 Array<Real> stem_xposns;
150 Drul_array<bool> dirs_found (0, 0);
152 for (int a = 2; a--;)
153 common[a] = common_refpoint_of_array (stems, me, Axis (a));
155 Grob *fvs = first_visible_stem (me);
156 Grob *lvs = last_visible_stem (me);
157 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
158 Real xr = fvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
161 We store some info to quickly interpolate.
163 Sometimes my head is screwed on backwards. The stemlength are
164 AFFINE linear in YL and YR. If YL == YR == 0, then we might have
165 stem_y != 0.0, when we're cross staff.
168 for (int i = 0; i < stems.size (); i++)
172 Stem_info si (Stem::get_stem_info (s));
174 stem_infos.push (si);
175 dirs_found[stem_infos.top ().dir_] = true;
177 bool f = to_boolean (s->get_property ("french-beaming"))
178 && s != lvs && s != fvs;
180 base_lengths.push (calc_stem_y (me, s, common, xl, xr,
181 Interval (0, 0), f) / ss);
182 stem_xposns.push (s->relative_coordinate (common[X_AXIS], X_AXIS));
188 Grob *commony = fvs->common_refpoint (lvs, Y_AXIS);
189 xstaff = Align_interface::has_interface (commony);
192 Direction ldir = Direction (stem_infos[0].dir_);
193 Direction rdir = Direction (stem_infos.top ().dir_);
194 bool is_knee = dirs_found[LEFT] && dirs_found[RIGHT];
196 int region_size = (int) parameters.REGION_SIZE;
199 Knees are harder, lets try some more possibilities for knees.
205 Asymetry ? should run to <= region_size ?
207 for (int i = -region_size; i < region_size; i++)
208 for (int j = 0; j < num_quants; j++)
210 quantsl.push (i + quants[j] + int (yl));
211 quantsr.push (i + quants[j] + int (yr));
214 Array<Quant_score> qscores;
216 for (int l = 0; l < quantsl.size (); l++)
217 for (int r = 0; r < quantsr.size (); r++)
227 /* This is a longish function, but we don't separate this out into
228 neat modular separate subfunctions, as the subfunctions would be
229 called for many values of YL, YR. By precomputing various
230 parameters outside of the loop, we can save a lot of time. */
231 for (int i = qscores.size (); i--;)
233 Real d = score_slopes_dy (qscores[i].yl, qscores[i].yr,
236 xstaff, ¶meters);
237 qscores[i].demerits += d;
240 qscores[i].score_card_ += to_string ("S%.2f", d);
244 Real rad = Staff_symbol_referencer::staff_radius (me);
245 Drul_array<int> edge_beam_counts
246 (Stem::beam_multiplicity (stems[0]).length () + 1,
247 Stem::beam_multiplicity (stems.top ()).length () + 1);
249 Real beam_translation = get_beam_translation (me) / ss;
251 Real reasonable_score = (is_knee) ? 200000 : 100;
252 for (int i = qscores.size (); i--;)
253 if (qscores[i].demerits < reasonable_score)
255 Real d = score_forbidden_quants (qscores[i].yl, qscores[i].yr,
256 rad, slt, thickness, beam_translation,
257 edge_beam_counts, ldir, rdir, ¶meters);
258 qscores[i].demerits += d;
261 qscores[i].score_card_ += to_string (" F %.2f", d);
265 for (int i = qscores.size (); i--;)
266 if (qscores[i].demerits < reasonable_score)
268 Real d = score_stem_lengths (stems, stem_infos,
269 base_lengths, stem_xposns,
272 qscores[i].yl, qscores[i].yr, ¶meters);
273 qscores[i].demerits += d;
276 qscores[i].score_card_ += to_string (" L %.2f", d);
280 int best_idx = best_quant_score_idx (qscores);
283 SCM inspect_quants = me->get_property ("inspect-quants");
284 if (to_boolean (me->get_layout ()->lookup_variable (ly_symbol2scm ("debug-beam-quanting")))
285 && scm_is_pair (inspect_quants))
287 Drul_array<Real> ins = ly_scm2interval (inspect_quants);
292 for (; i < qscores.size (); i++)
294 Real d = fabs (qscores[i].yl- ins[LEFT]) + fabs (qscores[i].yr - ins[RIGHT]);
302 programming_error ("can't find quant");
307 warning (_ ("no feasible beam position"));
308 me->set_property ("positions", ly_interval2scm (Interval (0, 0)));
311 me->set_property ("positions",
312 ly_interval2scm (Drul_array<Real> (qscores[best_idx].yl,
313 qscores[best_idx].yr)));
316 && to_boolean (me->get_layout ()->lookup_variable (ly_symbol2scm ("debug-beam-quanting"))))
318 qscores[best_idx].score_card_ += to_string ("i%d", best_idx);
321 me->set_property ("quant-score",
322 scm_makfrom0str (qscores[best_idx].score_card_.to_str0 ()));
326 return SCM_UNSPECIFIED;
330 Beam::score_stem_lengths (Link_array<Grob> const &stems,
331 Array<Stem_info> const &stem_infos,
332 Array<Real> const &base_stem_ys,
333 Array<Real> const &stem_xs,
338 Beam_quant_parameters const *parameters)
340 Real limit_penalty = parameters->STEM_LENGTH_LIMIT_PENALTY;
341 Drul_array<Real> score (0, 0);
342 Drul_array<int> count (0, 0);
344 for (int i = 0; i < stems.size (); i++)
347 if (Stem::is_invisible (s))
352 Real beam_y = dx ? yr * (x - xl) / dx + yl * (xr - x) / dx : (yr + yl) / 2;
353 Real current_y = beam_y + base_stem_ys[i];
354 Real length_pen = parameters->STEM_LENGTH_DEMERIT_FACTOR;
356 Stem_info info = stem_infos[i];
357 Direction d = info.dir_;
359 score[d] += limit_penalty * max (0.0, (d * (info.shortest_y_ - current_y)));
361 Real ideal_diff = d * (current_y - info.ideal_y_);
362 Real ideal_score = shrink_extra_weight (ideal_diff, 1.5);
364 /* We introduce a power, to make the scoring strictly
365 convex. Otherwise a symmetric knee beam (up/down/up/down)
366 does not have an optimum in the middle. */
368 ideal_score = pow (ideal_score, 1.1);
370 score[d] += length_pen * ideal_score;
378 score[d] /= max (count[d], 1);
380 while (flip (&d) != DOWN);
382 return score[LEFT] + score[RIGHT];
386 Beam::score_slopes_dy (Real yl, Real yr,
387 Real dy_mus, Real dy_damp,
391 Beam_quant_parameters const *parameters)
397 DAMPING_DIRECTION_PENALTY is a very harsh measure, while for
398 complex beaming patterns, horizontal is often a good choice.
400 TODO: find a way to incorporate the complexity of the beam in this
403 if (fabs (dy / dx) > parameters->ROUND_TO_ZERO_SLOPE
404 && sign (dy_damp) != sign (dy))
406 dem += parameters->DAMPING_DIRECTION_PENALTY;
409 dem += parameters->MUSICAL_DIRECTION_FACTOR *max (0.0, (fabs (dy) - fabs (dy_mus)));
411 Real slope_penalty = parameters->IDEAL_SLOPE_FACTOR;
413 /* Xstaff beams tend to use extreme slopes to get short stems. We
414 put in a penalty here. */
418 /* Huh, why would a too steep beam be better than a too flat one ? */
419 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy), 1.5)
428 return x - floor (x);
432 TODO: The fixed value SECONDARY_BEAM_DEMERIT is probably flawed:
433 because for 32nd and 64th beams the forbidden quants are relatively
434 more important than stem lengths.
437 Beam::score_forbidden_quants (Real yl, Real yr,
440 Real thickness, Real beam_translation,
441 Drul_array<int> beam_counts,
442 Direction ldir, Direction rdir,
444 Beam_quant_parameters const *parameters)
447 Drul_array<Real> y (yl, yr);
448 Drul_array<Direction> dirs (ldir, rdir);
450 Real extra_demerit = parameters->SECONDARY_BEAM_DEMERIT / (max (beam_counts[LEFT], beam_counts[RIGHT]));
454 Real eps = parameters->BEAM_EPS;
458 for (int j = 1; j <= beam_counts[d]; j++)
460 Direction stem_dir = dirs[d];
463 The 2.2 factor is to provide a little leniency for
464 borderline cases. If we do 2.0, then the upper outer line
465 will be in the gap of the (2, sit) quant, leading to a
468 Real gap1 = y[d] - stem_dir * ((j - 1) * beam_translation + thickness / 2 - slt / 2.2);
469 Real gap2 = y[d] - stem_dir * (j * beam_translation - thickness / 2 + slt / 2.2);
472 gap.add_point (gap1);
473 gap.add_point (gap2);
475 for (Real k = -radius;
476 k <= radius + eps; k += 1.0)
477 if (gap.contains (k))
479 Real dist = min (fabs (gap[UP] - k), fabs (gap[DOWN] - k));
482 this parameter is tuned to grace-stem-length.ly
484 Real fixed_demerit = 0.4;
488 + (1 - fixed_demerit) * (dist / gap.length ()) * 2);
492 while ((flip (&d)) != LEFT);
494 if (max (beam_counts[LEFT], beam_counts[RIGHT]) >= 2)
497 Real sit = (thickness - slt) / 2;
499 Real hang = 1.0 - (thickness - slt) / 2;
504 if (beam_counts[d] >= 2
505 && fabs (y[d] - dirs[d] * beam_translation) < radius + inter)
507 if (dirs[d] == UP && dy <= eps
508 && fabs (my_modf (y[d]) - sit) < eps)
509 dem += extra_demerit;
511 if (dirs[d] == DOWN && dy >= eps
512 && fabs (my_modf (y[d]) - hang) < eps)
513 dem += extra_demerit;
516 if (beam_counts[d] >= 3
517 && fabs (y[d] - 2 * dirs[d] * beam_translation) < radius + inter)
519 if (dirs[d] == UP && dy <= eps
520 && fabs (my_modf (y[d]) - straddle) < eps)
521 dem += extra_demerit;
523 if (dirs[d] == DOWN && dy >= eps
524 && fabs (my_modf (y[d]) - straddle) < eps)
525 dem += extra_demerit;
528 while (flip (&d) != LEFT);