2 beam-quanting.cc -- implement Beam quanting functions
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
15 #include "align-interface.hh"
16 #include "international.hh"
17 #include "output-def.hh"
18 #include "pointer-group-interface.hh"
19 #include "staff-symbol-referencer.hh"
24 get_detail (SCM alist, SCM sym, Real def)
26 SCM entry = scm_assq (sym, alist);
28 if (scm_is_pair (entry))
29 return robust_scm2double (scm_cdr (entry), def);
34 Beam_quant_parameters::fill (Grob *him)
36 SCM details = him->get_property ("details");
38 INTER_QUANT_PENALTY = get_detail (details, ly_symbol2scm ("inter-quant-penalty"), 1000.0);
39 SECONDARY_BEAM_DEMERIT = get_detail (details, ly_symbol2scm ("secondary-beam-demerit"), 10.0);
40 STEM_LENGTH_DEMERIT_FACTOR = get_detail (details, ly_symbol2scm ("stem-length-demerit-factor"), 5);
41 REGION_SIZE = get_detail (details, ly_symbol2scm ("region-size"), 2);
42 BEAM_EPS = get_detail (details, ly_symbol2scm ("beam-eps"), 1e-3);
44 // possibly ridiculous, but too short stems just won't do
45 STEM_LENGTH_LIMIT_PENALTY = get_detail (details, ly_symbol2scm ("stem-length-limit-penalty"), 5000);
46 DAMPING_DIRECTION_PENALTY = get_detail (details, ly_symbol2scm ("damping-direction-penalty"), 800);
47 MUSICAL_DIRECTION_FACTOR = get_detail (details, ly_symbol2scm ("musical-direction-factor"), 400);
48 IDEAL_SLOPE_FACTOR = get_detail (details, ly_symbol2scm ("ideal-slope-factor"), 10);
49 ROUND_TO_ZERO_SLOPE = get_detail (details, ly_symbol2scm ("round-to-zero-slope"), 0.02);
53 shrink_extra_weight (Real x, Real fac)
55 return fabs (x) * ((x < 0) ? fac : 1.0);
65 std::string score_card_;
72 - Make all demerits customisable
74 - One sensible check per demerit (what's this --hwn)
76 - Add demerits for quants per se, as to forbid a specific quant
81 best_quant_score_idx (std::vector<Quant_score> const &qscores)
85 for (vsize i = qscores.size (); i--;)
87 if (qscores[i].demerits < best)
89 best = qscores [i].demerits;
97 MAKE_SCHEME_CALLBACK (Beam, quanting, 2);
99 Beam::quanting (SCM smob, SCM posns)
101 Grob *me = unsmob_grob (smob);
103 Beam_quant_parameters parameters;
104 parameters.fill (me);
106 Real yl = scm_to_double (scm_car (posns));
107 Real yr = scm_to_double (scm_cdr (posns));
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));
126 std::vector<Real> quantsl;
127 std::vector<Real> quantsr;
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 std::vector<Stem_info> stem_infos;
145 std::vector<Real> base_lengths;
146 std::vector<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 (vsize i = 0; i < stems.size (); i++)
170 Stem_info si (Stem::get_stem_info (s));
172 stem_infos.push_back (si);
173 dirs_found[stem_infos.back ().dir_] = true;
175 bool f = to_boolean (s->get_property ("french-beaming"))
176 && s != lvs && s != fvs;
178 base_lengths.push_back (calc_stem_y (me, s, common, xl, xr,
179 Interval (0, 0), f) / ss);
180 stem_xposns.push_back (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.back ().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_back (i + quants[j] + int (yl));
209 quantsr.push_back (i + quants[j] + int (yr));
212 std::vector<Quant_score> qscores;
214 for (vsize l = 0; l < quantsl.size (); l++)
215 for (vsize r = 0; r < quantsr.size (); r++)
222 qscores.push_back (qs);
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 (vsize 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.back ()).length () + 1);
247 Real beam_translation = get_beam_translation (me) / ss;
249 Real reasonable_score = (is_knee) ? 200000 : 100;
250 for (vsize 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 (vsize 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->layout ()->lookup_variable (ly_symbol2scm ("debug-beam-quanting")))
283 && scm_is_pair (inspect_quants))
285 Drul_array<Real> ins = ly_scm2interval (inspect_quants);
288 for (vsize i = 0; i < qscores.size (); i++)
290 Real d = fabs (qscores[i].yl- ins[LEFT]) + fabs (qscores[i].yr - ins[RIGHT]);
298 programming_error ("can't find quant");
302 Interval final_positions;
305 warning (_ ("no feasible beam position"));
306 final_positions = Interval (0, 0);
310 final_positions = Drul_array<Real> (qscores[best_idx].yl,
311 qscores[best_idx].yr);
316 && to_boolean (me->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_.c_str ()));
326 return ly_interval2scm (final_positions);
330 Beam::score_stem_lengths (Link_array__Grob_ const &stems,
331 std::vector<Stem_info> const &stem_infos,
332 std::vector<Real> const &base_stem_ys,
333 std::vector<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 (vsize 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;
377 score[d] /= max (count[d], 1);
378 while (flip (&d) != DOWN)
381 return score[LEFT] + score[RIGHT];
385 Beam::score_slopes_dy (Real yl, Real yr,
386 Real dy_mus, Real dy_damp,
390 Beam_quant_parameters const *parameters)
396 DAMPING_DIRECTION_PENALTY is a very harsh measure, while for
397 complex beaming patterns, horizontal is often a good choice.
399 TODO: find a way to incorporate the complexity of the beam in this
402 if (fabs (dy / dx) > parameters->ROUND_TO_ZERO_SLOPE
403 && sign (dy_damp) != sign (dy))
404 dem += parameters->DAMPING_DIRECTION_PENALTY;
406 dem += parameters->MUSICAL_DIRECTION_FACTOR
407 * max (0.0, (fabs (dy) - fabs (dy_mus)));
409 Real slope_penalty = parameters->IDEAL_SLOPE_FACTOR;
411 /* Xstaff beams tend to use extreme slopes to get short stems. We
412 put in a penalty here. */
416 /* Huh, why would a too steep beam be better than a too flat one ? */
417 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy), 1.5)
426 return x - floor (x);
430 TODO: The fixed value SECONDARY_BEAM_DEMERIT is probably flawed:
431 because for 32nd and 64th beams the forbidden quants are relatively
432 more important than stem lengths.
435 Beam::score_forbidden_quants (Real yl, Real yr,
438 Real thickness, Real beam_translation,
439 Drul_array<int> beam_counts,
440 Direction ldir, Direction rdir,
442 Beam_quant_parameters const *parameters)
445 Drul_array<Real> y (yl, yr);
446 Drul_array<Direction> dirs (ldir, rdir);
448 Real extra_demerit = parameters->SECONDARY_BEAM_DEMERIT / (max (beam_counts[LEFT], beam_counts[RIGHT]));
452 Real eps = parameters->BEAM_EPS;
456 for (int j = 1; j <= beam_counts[d]; j++)
458 Direction stem_dir = dirs[d];
461 The 2.2 factor is to provide a little leniency for
462 borderline cases. If we do 2.0, then the upper outer line
463 will be in the gap of the (2, sit) quant, leading to a
466 Real gap1 = y[d] - stem_dir * ((j - 1) * beam_translation + thickness / 2 - slt / 2.2);
467 Real gap2 = y[d] - stem_dir * (j * beam_translation - thickness / 2 + slt / 2.2);
470 gap.add_point (gap1);
471 gap.add_point (gap2);
473 for (Real k = -radius;
474 k <= radius + eps; k += 1.0)
475 if (gap.contains (k))
477 Real dist = min (fabs (gap[UP] - k), fabs (gap[DOWN] - k));
480 this parameter is tuned to grace-stem-length.ly
482 Real fixed_demerit = 0.4;
486 + (1 - fixed_demerit) * (dist / gap.length ()) * 2);
490 while ((flip (&d)) != LEFT);
492 if (max (beam_counts[LEFT], beam_counts[RIGHT]) >= 2)
495 Real sit = (thickness - slt) / 2;
497 Real hang = 1.0 - (thickness - slt) / 2;
502 if (beam_counts[d] >= 2
503 && fabs (y[d] - dirs[d] * beam_translation) < radius + inter)
505 if (dirs[d] == UP && dy <= eps
506 && fabs (my_modf (y[d]) - sit) < eps)
507 dem += extra_demerit;
509 if (dirs[d] == DOWN && dy >= eps
510 && fabs (my_modf (y[d]) - hang) < eps)
511 dem += extra_demerit;
514 if (beam_counts[d] >= 3
515 && fabs (y[d] - 2 * dirs[d] * beam_translation) < radius + inter)
517 if (dirs[d] == UP && dy <= eps
518 && fabs (my_modf (y[d]) - straddle) < eps)
519 dem += extra_demerit;
521 if (dirs[d] == DOWN && dy >= eps
522 && fabs (my_modf (y[d]) - straddle) < eps)
523 dem += extra_demerit;
526 while (flip (&d) != LEFT);