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))
28 return robust_scm2double (scm_cdr (entry), def);
33 Beam_quant_parameters::fill (Grob *him)
35 SCM details = him->get_property ("details");
37 INTER_QUANT_PENALTY = get_detail (details, ly_symbol2scm ("inter-quant-penalty"), 1000.0);
38 SECONDARY_BEAM_DEMERIT = get_detail (details, ly_symbol2scm ("secondary-beam-demerit"), 10.0);
39 STEM_LENGTH_DEMERIT_FACTOR = get_detail (details, ly_symbol2scm ("stem-length-demerit-factor"), 5);
40 REGION_SIZE = get_detail (details, ly_symbol2scm ("region-size"), 2);
41 BEAM_EPS = get_detail (details, ly_symbol2scm ("beam-eps"), 1e-3);
43 // possibly ridiculous, but too short stems just won't do
44 STEM_LENGTH_LIMIT_PENALTY = get_detail (details, ly_symbol2scm ("stem-length-limit-penalty"), 5000);
45 DAMPING_DIRECTION_PENALTY = get_detail (details, ly_symbol2scm ("damping-direction-penalty"), 800);
46 MUSICAL_DIRECTION_FACTOR = get_detail (details, ly_symbol2scm ("musical-direction-factor"), 400);
47 IDEAL_SLOPE_FACTOR = get_detail (details, ly_symbol2scm ("ideal-slope-factor"), 10);
48 ROUND_TO_ZERO_SLOPE = get_detail (details, ly_symbol2scm ("round-to-zero-slope"), 0.02);
52 shrink_extra_weight (Real x, Real fac)
54 return fabs (x) * ((x < 0) ? fac : 1.0);
71 - Make all demerits customisable
73 - One sensible check per demerit (what's this --hwn)
75 - Add demerits for quants per se, as to forbid a specific quant
80 best_quant_score_idx (Array<Quant_score> const &qscores)
84 for (int i = qscores.size (); i--;)
86 if (qscores[i].demerits < best)
88 best = qscores [i].demerits;
96 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
98 Beam::quanting (SCM smob)
100 Grob *me = unsmob_grob (smob);
102 Beam_quant_parameters parameters;
103 parameters.fill (me);
105 SCM s = me->get_property ("positions");
106 Real yl = scm_to_double (scm_car (s));
107 Real yr = scm_to_double (scm_cdr (s));
110 Calculations are relative to a unit-scaled staff, i.e. the quants are
111 divided by the current staff_space.
114 Real ss = Staff_symbol_referencer::staff_space (me);
115 Real thickness = Beam::get_thickness (me) / ss;
116 Real slt = Staff_symbol_referencer::line_thickness (me) / ss;
118 Real dy_mus = robust_scm2double (me->get_property ("least-squares-dy"), 0);
120 Real sit = (thickness - slt) / 2;
122 Real hang = 1.0 - (thickness - slt) / 2;
123 Real quants [] = {straddle, sit, inter, hang };
125 int num_quants = int (sizeof (quants) / sizeof (Real));
130 going to REGION_SIZE == 2, yields another 0.6 second with
134 (result indexes between 70 and 575) ? --hwn.
139 Do stem computations. These depend on YL and YR linearly, so we can
140 precompute for every stem 2 factors.
142 Link_array<Grob> stems
143 = extract_grob_array (me, "stems");
144 Array<Stem_info> stem_infos;
145 Array<Real> base_lengths;
146 Array<Real> stem_xposns;
148 Drul_array<bool> dirs_found (0, 0);
150 for (int a = 2; a--;)
151 common[a] = common_refpoint_of_array (stems, me, Axis (a));
153 Grob *fvs = first_visible_stem (me);
154 Grob *lvs = last_visible_stem (me);
155 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
156 Real xr = fvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
159 We store some info to quickly interpolate.
161 Sometimes my head is screwed on backwards. The stemlength are
162 AFFINE linear in YL and YR. If YL == YR == 0, then we might have
163 stem_y != 0.0, when we're cross staff.
166 for (int i = 0; i < stems.size (); i++)
170 Stem_info si (Stem::get_stem_info (s));
172 stem_infos.push (si);
173 dirs_found[stem_infos.top ().dir_] = true;
175 bool f = to_boolean (s->get_property ("french-beaming"))
176 && s != lvs && s != fvs;
178 base_lengths.push (calc_stem_y (me, s, common, xl, xr,
179 Interval (0, 0), f) / ss);
180 stem_xposns.push (s->relative_coordinate (common[X_AXIS], X_AXIS));
186 Grob *commony = fvs->common_refpoint (lvs, Y_AXIS);
187 xstaff = Align_interface::has_interface (commony);
190 Direction ldir = Direction (stem_infos[0].dir_);
191 Direction rdir = Direction (stem_infos.top ().dir_);
192 bool is_knee = dirs_found[LEFT] && dirs_found[RIGHT];
194 int region_size = (int) parameters.REGION_SIZE;
197 Knees are harder, lets try some more possibilities for knees.
203 Asymetry ? should run to <= region_size ?
205 for (int i = -region_size; i < region_size; i++)
206 for (int j = 0; j < num_quants; j++)
208 quantsl.push (i + quants[j] + int (yl));
209 quantsr.push (i + quants[j] + int (yr));
212 Array<Quant_score> qscores;
214 for (int l = 0; l < quantsl.size (); l++)
215 for (int r = 0; r < quantsr.size (); r++)
225 /* This is a longish function, but we don't separate this out into
226 neat modular separate subfunctions, as the subfunctions would be
227 called for many values of YL, YR. By precomputing various
228 parameters outside of the loop, we can save a lot of time. */
229 for (int i = qscores.size (); i--;)
231 Real d = score_slopes_dy (qscores[i].yl, qscores[i].yr,
234 xstaff, ¶meters);
235 qscores[i].demerits += d;
238 qscores[i].score_card_ += to_string ("S%.2f", d);
242 Real rad = Staff_symbol_referencer::staff_radius (me);
243 Drul_array<int> edge_beam_counts
244 (Stem::beam_multiplicity (stems[0]).length () + 1,
245 Stem::beam_multiplicity (stems.top ()).length () + 1);
247 Real beam_translation = get_beam_translation (me) / ss;
249 Real reasonable_score = (is_knee) ? 200000 : 100;
250 for (int i = qscores.size (); i--;)
251 if (qscores[i].demerits < reasonable_score)
253 Real d = score_forbidden_quants (qscores[i].yl, qscores[i].yr,
254 rad, slt, thickness, beam_translation,
255 edge_beam_counts, ldir, rdir, ¶meters);
256 qscores[i].demerits += d;
259 qscores[i].score_card_ += to_string (" F %.2f", d);
263 for (int i = qscores.size (); i--;)
264 if (qscores[i].demerits < reasonable_score)
266 Real d = score_stem_lengths (stems, stem_infos,
267 base_lengths, stem_xposns,
270 qscores[i].yl, qscores[i].yr, ¶meters);
271 qscores[i].demerits += d;
274 qscores[i].score_card_ += to_string (" L %.2f", d);
278 int best_idx = best_quant_score_idx (qscores);
281 SCM inspect_quants = me->get_property ("inspect-quants");
282 if (to_boolean (me->get_layout ()->lookup_variable (ly_symbol2scm ("debug-beam-quanting")))
283 && scm_is_pair (inspect_quants))
285 Drul_array<Real> ins = ly_scm2interval (inspect_quants);
290 for (; i < qscores.size (); i++)
292 Real d = fabs (qscores[i].yl- ins[LEFT]) + fabs (qscores[i].yr - ins[RIGHT]);
300 programming_error ("can't find quant");
305 warning (_ ("no feasible beam position"));
306 me->set_property ("positions", ly_interval2scm (Interval (0, 0)));
309 me->set_property ("positions",
310 ly_interval2scm (Drul_array<Real> (qscores[best_idx].yl,
311 qscores[best_idx].yr)));
314 && to_boolean (me->get_layout ()->lookup_variable (ly_symbol2scm ("debug-beam-quanting"))))
316 qscores[best_idx].score_card_ += to_string ("i%d", best_idx);
319 me->set_property ("quant-score",
320 scm_makfrom0str (qscores[best_idx].score_card_.to_str0 ()));
324 return SCM_UNSPECIFIED;
328 Beam::score_stem_lengths (Link_array<Grob> const &stems,
329 Array<Stem_info> const &stem_infos,
330 Array<Real> const &base_stem_ys,
331 Array<Real> const &stem_xs,
336 Beam_quant_parameters const *parameters)
338 Real limit_penalty = parameters->STEM_LENGTH_LIMIT_PENALTY;
339 Drul_array<Real> score (0, 0);
340 Drul_array<int> count (0, 0);
342 for (int i = 0; i < stems.size (); i++)
345 if (Stem::is_invisible (s))
350 Real beam_y = dx ? yr * (x - xl) / dx + yl * (xr - x) / dx : (yr + yl) / 2;
351 Real current_y = beam_y + base_stem_ys[i];
352 Real length_pen = parameters->STEM_LENGTH_DEMERIT_FACTOR;
354 Stem_info info = stem_infos[i];
355 Direction d = info.dir_;
357 score[d] += limit_penalty * max (0.0, (d * (info.shortest_y_ - current_y)));
359 Real ideal_diff = d * (current_y - info.ideal_y_);
360 Real ideal_score = shrink_extra_weight (ideal_diff, 1.5);
362 /* We introduce a power, to make the scoring strictly
363 convex. Otherwise a symmetric knee beam (up/down/up/down)
364 does not have an optimum in the middle. */
366 ideal_score = pow (ideal_score, 1.1);
368 score[d] += length_pen * ideal_score;
375 score[d] /= max (count[d], 1);
376 while (flip (&d) != DOWN)
379 return score[LEFT] + score[RIGHT];
383 Beam::score_slopes_dy (Real yl, Real yr,
384 Real dy_mus, Real dy_damp,
388 Beam_quant_parameters const *parameters)
394 DAMPING_DIRECTION_PENALTY is a very harsh measure, while for
395 complex beaming patterns, horizontal is often a good choice.
397 TODO: find a way to incorporate the complexity of the beam in this
400 if (fabs (dy / dx) > parameters->ROUND_TO_ZERO_SLOPE
401 && sign (dy_damp) != sign (dy))
402 dem += parameters->DAMPING_DIRECTION_PENALTY;
404 dem += parameters->MUSICAL_DIRECTION_FACTOR *max (0.0, (fabs (dy) - fabs (dy_mus)));
406 Real slope_penalty = parameters->IDEAL_SLOPE_FACTOR;
408 /* Xstaff beams tend to use extreme slopes to get short stems. We
409 put in a penalty here. */
413 /* Huh, why would a too steep beam be better than a too flat one ? */
414 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy), 1.5)
423 return x - floor (x);
427 TODO: The fixed value SECONDARY_BEAM_DEMERIT is probably flawed:
428 because for 32nd and 64th beams the forbidden quants are relatively
429 more important than stem lengths.
432 Beam::score_forbidden_quants (Real yl, Real yr,
435 Real thickness, Real beam_translation,
436 Drul_array<int> beam_counts,
437 Direction ldir, Direction rdir,
439 Beam_quant_parameters const *parameters)
442 Drul_array<Real> y (yl, yr);
443 Drul_array<Direction> dirs (ldir, rdir);
445 Real extra_demerit = parameters->SECONDARY_BEAM_DEMERIT / (max (beam_counts[LEFT], beam_counts[RIGHT]));
449 Real eps = parameters->BEAM_EPS;
453 for (int j = 1; j <= beam_counts[d]; j++)
455 Direction stem_dir = dirs[d];
458 The 2.2 factor is to provide a little leniency for
459 borderline cases. If we do 2.0, then the upper outer line
460 will be in the gap of the (2, sit) quant, leading to a
463 Real gap1 = y[d] - stem_dir * ((j - 1) * beam_translation + thickness / 2 - slt / 2.2);
464 Real gap2 = y[d] - stem_dir * (j * beam_translation - thickness / 2 + slt / 2.2);
467 gap.add_point (gap1);
468 gap.add_point (gap2);
470 for (Real k = -radius;
471 k <= radius + eps; k += 1.0)
472 if (gap.contains (k))
474 Real dist = min (fabs (gap[UP] - k), fabs (gap[DOWN] - k));
477 this parameter is tuned to grace-stem-length.ly
479 Real fixed_demerit = 0.4;
483 + (1 - fixed_demerit) * (dist / gap.length ()) * 2);
487 while ((flip (&d)) != LEFT);
489 if (max (beam_counts[LEFT], beam_counts[RIGHT]) >= 2)
492 Real sit = (thickness - slt) / 2;
494 Real hang = 1.0 - (thickness - slt) / 2;
499 if (beam_counts[d] >= 2
500 && fabs (y[d] - dirs[d] * beam_translation) < radius + inter)
502 if (dirs[d] == UP && dy <= eps
503 && fabs (my_modf (y[d]) - sit) < eps)
504 dem += extra_demerit;
506 if (dirs[d] == DOWN && dy >= eps
507 && fabs (my_modf (y[d]) - hang) < eps)
508 dem += extra_demerit;
511 if (beam_counts[d] >= 3
512 && fabs (y[d] - 2 * dirs[d] * beam_translation) < radius + inter)
514 if (dirs[d] == UP && dy <= eps
515 && fabs (my_modf (y[d]) - straddle) < eps)
516 dem += extra_demerit;
518 if (dirs[d] == DOWN && dy >= eps
519 && fabs (my_modf (y[d]) - straddle) < eps)
520 dem += extra_demerit;
523 while (flip (&d) != LEFT);