2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
18 * Remove #'direction from beam. A beam has no direction per se.
19 It may only set directions for stems.
23 * Use Number_pair i.s.o Interval to represent (yl, yr).
29 #include <math.h> // tanh.
31 #include "molecule.hh"
32 #include "directional-element-interface.hh"
36 #include "least-squares.hh"
38 #include "paper-def.hh"
40 #include "group-interface.hh"
41 #include "staff-symbol-referencer.hh"
47 #define DEBUG_QUANTING 0
51 #include "text-item.hh" // debug output.
52 #include "font-interface.hh" // debug output.
56 const int INTER_QUANT_PENALTY = 1000;
57 const int SECONDARY_BEAM_DEMERIT = 15;
58 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
59 // possibly ridiculous, but too short stems just won't do
60 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
61 const int DAMPING_DIRECTIION_PENALTY = 800;
62 const int MUSICAL_DIRECTION_FACTOR = 400;
63 const int IDEAL_SLOPE_FACTOR = 10;
64 const int REGION_SIZE = 2;
68 shrink_extra_weight (Real x)
70 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
74 Beam::add_stem (Grob *me, Grob *s)
76 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
78 s->add_dependency (me);
80 assert (!Stem::beam_l (s));
81 s->set_grob_property ("beam", me->self_scm ());
83 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
87 Beam::get_interbeam (Grob *me)
89 SCM func = me->get_grob_property ("space-function");
90 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
91 return gh_scm2double (s);
98 Beam::get_multiplicity (Grob *me)
101 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
103 Grob *sc = unsmob_grob (ly_car (s));
105 if (Stem::has_interface (sc))
106 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
111 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
113 Beam::space_function (SCM smob, SCM multiplicity)
115 Grob *me = unsmob_grob (smob);
117 Real staff_space = Staff_symbol_referencer::staff_space (me);
118 Real line = me->paper_l ()->get_var ("linethickness");
119 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
122 Real interbeam = gh_scm2int (multiplicity) < 4
123 ? (2*staff_space + line - thickness) / 2.0
124 : (3*staff_space + line - thickness) / 3.0;
126 return gh_double2scm (interbeam);
130 /* After pre-processing all directions should be set.
131 Several post-processing routines (stem, slur, script) need stem/beam
133 Currenly, this means that beam has set all stem's directions.
134 [Alternatively, stems could set its own directions, according to
135 their beam, during 'final-pre-processing'.] */
136 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
138 Beam::before_line_breaking (SCM smob)
140 Grob *me = unsmob_grob (smob);
142 /* Beams with less than 2 two stems don't make much sense, but could happen
147 For a beam that only has one stem, we try to do some disappearance magic:
148 we revert the flag, and move on to The Eternal Engraving Fields. */
150 int count = visible_stem_count (me);
153 me->warning (_ ("beam has less than two visible stems"));
155 SCM stems = me->get_grob_property ("stems");
156 if (scm_ilength (stems) == 1)
158 me->warning (_ ("Beam has less than two stems. Removing beam."));
160 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
163 return SCM_UNSPECIFIED;
165 else if (scm_ilength (stems) == 0)
168 return SCM_UNSPECIFIED;
173 Direction d = get_default_dir (me);
175 consider_auto_knees (me, d);
176 set_stem_directions (me, d);
177 set_stem_shorten (me);
184 Beam::get_default_dir (Grob *me)
186 Drul_array<int> total;
187 total[UP] = total[DOWN] = 0;
188 Drul_array<int> count;
189 count[UP] = count[DOWN] = 0;
192 Link_array<Item> stems=
193 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
195 for (int i=0; i <stems.size (); i++)
198 Direction sd = Directional_element_interface::get (s);
200 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
201 int current = sd ? (1 + d * sd)/2 : center_distance;
208 } while (flip (&d) != DOWN);
210 SCM func = me->get_grob_property ("dir-function");
211 SCM s = gh_call2 (func,
212 gh_cons (gh_int2scm (count[UP]),
213 gh_int2scm (count[DOWN])),
214 gh_cons (gh_int2scm (total[UP]),
215 gh_int2scm (total[DOWN])));
217 if (gh_number_p (s) && gh_scm2int (s))
220 /* If dir is not determined: get default */
221 return to_dir (me->get_grob_property ("neutral-direction"));
225 /* Set all stems with non-forced direction to beam direction.
226 Urg: non-forced should become `without/with unforced' direction,
227 once stem gets cleaned-up. */
229 Beam::set_stem_directions (Grob *me, Direction d)
231 Link_array<Item> stems
232 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
234 for (int i=0; i <stems.size (); i++)
237 SCM force = s->remove_grob_property ("dir-forced");
238 if (!gh_boolean_p (force) || !gh_scm2bool (force))
239 Directional_element_interface::set (s, d);
243 /* Simplistic auto-knees; only consider vertical gap between two
246 `Forced' stem directions are ignored. If you don't want auto-knees,
247 don't set, or unset auto-knee-gap. */
249 Beam::consider_auto_knees (Grob *me, Direction d)
251 SCM scm = me->get_grob_property ("auto-knee-gap");
253 if (gh_number_p (scm))
257 Real staff_space = Staff_symbol_referencer::staff_space (me);
258 Real gap = gh_scm2double (scm) / staff_space;
261 Link_array<Item> stems=
262 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
264 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
265 for (int i=1; i < stems.size (); i++)
266 if (!Stem::invisible_b (stems[i]))
267 common = common->common_refpoint (stems[i], Y_AXIS);
270 for (int i=1; i < stems.size (); i++)
272 if (!Stem::invisible_b (stems[i-1]))
274 if (Stem::invisible_b (stems[l]))
276 if (Stem::invisible_b (stems[i]))
279 Real left = Stem::extremal_heads (stems[l])[d]
280 ->relative_coordinate (common, Y_AXIS);
281 Real right = Stem::extremal_heads (stems[i])[-d]
282 ->relative_coordinate (common, Y_AXIS);
284 Real dy = right - left;
288 knee_y = (right + left) / 2;
296 for (int i=0; i < stems.size (); i++)
298 if (Stem::invisible_b (stems[i]))
301 Real y = Stem::extremal_heads (stems[i])[d]
302 ->relative_coordinate (common, Y_AXIS);
304 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
305 s->set_grob_property ("dir-forced", SCM_BOOL_T);
311 /* Set stem's shorten property if unset.
314 take some y-position (chord/beam/nearest?) into account
315 scmify forced-fraction
319 why is shorten stored in beam, and not directly in stem?
323 Beam::set_stem_shorten (Grob *m)
325 Spanner*me = dynamic_cast<Spanner*> (m);
327 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
329 int multiplicity = get_multiplicity (me);
331 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
332 if (shorten == SCM_EOL)
335 int sz = scm_ilength (shorten);
337 Real staff_space = Staff_symbol_referencer::staff_space (me);
338 SCM shorten_elt = scm_list_ref (shorten,
339 gh_int2scm (multiplicity <? (sz - 1)));
340 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
342 /* your similar cute comment here */
343 shorten_f *= forced_fraction;
346 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
349 /* Call list of y-dy-callbacks, that handle setting of
350 grob-properties y, dy.
352 User may set grob-properties: y-position-hs and height-hs
353 (to be fixed) that override the calculated y and dy.
355 Because y and dy cannot be calculated and quanted separately, we
356 always calculate both, then check for user override. */
357 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
359 Beam::after_line_breaking (SCM smob)
361 Grob *me = unsmob_grob (smob);
363 /* Copy to mutable list. */
364 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
365 me->set_grob_property ("positions", s);
367 if (ly_car (s) != SCM_BOOL_F)
368 return SCM_UNSPECIFIED;
370 // one wonders if such genericity is necessary --hwn.
371 SCM callbacks = me->get_grob_property ("position-callbacks");
372 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
373 gh_call1 (ly_car (i), smob);
375 set_stem_lengths (me);
376 return SCM_UNSPECIFIED;
390 - Make all demerits customisable
392 - One sensible check per demerit (what's this --hwn)
394 - Add demerits for quants per se, as to forbid a specific quant
398 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
400 Beam::quanting (SCM smob)
402 Grob *me = unsmob_grob (smob);
404 SCM s = me->get_grob_property ("positions");
405 Real yl = gh_scm2double (gh_car (s));
406 Real yr = gh_scm2double (gh_cdr (s));
408 Real ss = Staff_symbol_referencer::staff_space (me);
409 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
410 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
413 SCM sdy = me->get_grob_property ("least-squares-dy");
414 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
417 Real sit = (thickness - slt) / 2;
419 Real hang = 1.0 - (thickness - slt) / 2;
420 Real quants [] = {straddle, sit, inter, hang };
422 int num_quants = int (sizeof (quants)/sizeof (Real));
427 going to REGION_SIZE == 2, yields another 0.6 second with
431 (result indexes between 70 and 575) ? --hwn.
438 Do stem computations. These depend on YL and YR linearly, so we can
439 precompute for every stem 2 factors.
441 Link_array<Grob> stems=
442 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
443 Array<Stem_info> stem_infos;
444 Array<Real> lbase_lengths;
445 Array<Real> rbase_lengths;
447 Drul_array<bool> dirs_found(0,0);
448 for (int i= 0; i < stems.size(); i++)
451 stem_infos.push (Stem::calc_stem_info (s));
452 dirs_found[stem_infos.top ().dir_] = true;
454 Real b = calc_stem_y (me, s, Interval (1,0), false);
455 lbase_lengths.push (b);
457 Real a = calc_stem_y (me, s, Interval (0,1), false);
458 rbase_lengths.push (a);
461 Direction ldir = Direction (stem_infos[0].dir_);
462 Direction rdir = Direction (stem_infos.top ().dir_);
463 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
466 int region_size = REGION_SIZE;
468 Knees are harder, lets try some more possibilities for knees.
473 for (int i = -region_size ; i < region_size; i++)
474 for (int j = 0; j < num_quants; j++)
476 quantsl.push (i + quants[j] + int (yl));
477 quantsr.push (i + quants[j] + int (yr));
480 Array<Quant_score> qscores;
482 for (int l =0; l < quantsl.size (); l++)
483 for (int r =0; r < quantsr.size (); r++)
495 This is a longish function, but we don't separate this out into
496 neat modular separate subfunctions, as the subfunctions would be
497 called for many values of YL, YR. By precomputing various
498 parameters outside of the loop, we can save a lot of time.
501 for (int i = qscores.size (); i--;)
502 if (qscores[i].demerits < 100)
505 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
509 Real rad = Staff_symbol_referencer::staff_radius (me);
510 int multiplicity = get_multiplicity (me);
511 Real interbeam = multiplicity < 4
512 ? (2*ss + slt - thickness) / 2.0
513 : (3*ss + slt - thickness) / 3.0;
515 for (int i = qscores.size (); i--;)
516 if (qscores[i].demerits < 100)
519 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
520 rad, slt, thickness, interbeam,
521 multiplicity, ldir, rdir);
525 for (int i = qscores.size (); i--;)
526 if (qscores[i].demerits < 100)
529 += score_stem_lengths (stems, stem_infos,
530 lbase_lengths, rbase_lengths,
532 me, qscores[i].yl, qscores[i].yr);
538 for (int i = qscores.size (); i--;)
540 if (qscores[i].demerits < best)
542 best = qscores [i].demerits ;
548 me->set_grob_property ("positions",
549 gh_cons (gh_double2scm (qscores[best_idx].yl),
550 gh_double2scm (qscores[best_idx].yr))
556 me->set_grob_property ("quant-score",
557 gh_double2scm (qscores[best_idx].demerits));
558 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
561 return SCM_UNSPECIFIED;
565 Beam::score_stem_lengths (Link_array<Grob>stems,
566 Array<Stem_info> stem_infos,
567 Array<Real> left_factor,
568 Array<Real> right_factor,
573 Real demerit_score = 0.0 ;
574 Real pen = STEM_LENGTH_LIMIT_PENALTY;
576 for (int i=0; i < stems.size (); i++)
579 if (Stem::invisible_b (s))
583 yl * left_factor[i] + right_factor[i]* yr;
585 Stem_info info = stem_infos[i];
586 Direction d = info.dir_;
589 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
591 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
592 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
595 demerit_score *= 2.0 / stems.size ();
597 return demerit_score;
601 Beam::score_slopes_dy (Grob *me,
603 Real dy_mus, Real dy_damp)
608 if (sign (dy_damp) != sign (dy))
610 dem += DAMPING_DIRECTIION_PENALTY;
613 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
614 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
622 return x - floor (x);
626 Beam::score_forbidden_quants (Grob*me,
630 Real thickness, Real interbeam,
632 Direction ldir, Direction rdir)
637 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
638 dem += INTER_QUANT_PENALTY;
639 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
640 dem += INTER_QUANT_PENALTY;
642 // todo: use multiplicity of outer stems.
643 if (multiplicity >= 2)
647 Real sit = (thickness - slt) / 2;
649 Real hang = 1.0 - (thickness - slt) / 2;
652 if (fabs (yl - ldir * interbeam) < rad
653 && fabs (my_modf (yl) - inter) < 1e-3)
654 dem += SECONDARY_BEAM_DEMERIT;
655 if (fabs (yr - rdir * interbeam) < rad
656 && fabs (my_modf (yr) - inter) < 1e-3)
657 dem += SECONDARY_BEAM_DEMERIT;
662 Can't we simply compute the distance between the nearest
663 staffline and the secondary beam? That would get rid of the
664 silly case analysis here (which is probably not when we have
665 different beam-thicknesses.)
671 // hmm, without Interval/Drul_array, you get ~ 4x same code...
672 if (fabs (yl - ldir * interbeam) < rad + inter)
674 if (ldir == UP && dy <= eps
675 && fabs (my_modf (yl) - sit) < eps)
676 dem += SECONDARY_BEAM_DEMERIT;
678 if (ldir == DOWN && dy >= eps
679 && fabs (my_modf (yl) - hang) < eps)
680 dem += SECONDARY_BEAM_DEMERIT;
683 if (fabs (yr - rdir * interbeam) < rad + inter)
685 if (rdir == UP && dy >= eps
686 && fabs (my_modf (yr) - sit) < eps)
687 dem += SECONDARY_BEAM_DEMERIT;
689 if (rdir == DOWN && dy <= eps
690 && fabs (my_modf (yr) - hang) < eps)
691 dem += SECONDARY_BEAM_DEMERIT;
694 if (multiplicity >= 3)
696 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
698 if (ldir == UP && dy <= eps
699 && fabs (my_modf (yl) - straddle) < eps)
700 dem += SECONDARY_BEAM_DEMERIT;
702 if (ldir == DOWN && dy >= eps
703 && fabs (my_modf (yl) - straddle) < eps)
704 dem += SECONDARY_BEAM_DEMERIT;
707 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
709 if (rdir == UP && dy >= eps
710 && fabs (my_modf (yr) - straddle) < eps)
711 dem += SECONDARY_BEAM_DEMERIT;
713 if (rdir == DOWN && dy <= eps
714 && fabs (my_modf (yr) - straddle) < eps)
715 dem += SECONDARY_BEAM_DEMERIT;
725 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
727 Beam::least_squares (SCM smob)
729 Grob *me = unsmob_grob (smob);
731 int count = visible_stem_count (me);
736 me->set_grob_property ("positions", ly_interval2scm (pos));
737 return SCM_UNSPECIFIED;
740 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
741 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
745 Array<Real> x_posns ;
746 Link_array<Item> stems=
747 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
748 Grob *common = stems[0];
749 for (int i=1; i < stems.size (); i++)
750 common = stems[i]->common_refpoint (common, X_AXIS);
752 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
753 for (int i=0; i < stems.size (); i++)
757 Real x = s->relative_coordinate (common, X_AXIS) - x0;
760 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
768 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
769 Stem::chord_start_y (last_visible_stem (me)));
773 TODO -- use scoring for this.
775 complicated, because we take stem-info.ideal for determining
778 /* Make simple beam on middle line have small tilt */
779 if (!ideal[LEFT] && chord.delta () && count == 2)
785 Direction d = (Direction) (sign (chord.delta ()) * UP);
786 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
801 Array<Offset> ideals;
802 for (int i=0; i < stems.size (); i++)
805 if (Stem::invisible_b (s))
807 ideals.push (Offset (x_posns[i],
808 Stem::calc_stem_info (s).ideal_y_));
810 minimise_least_squares (&dydx, &y, ideals);
813 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
814 pos = Interval (y, (y+dy));
817 me->set_grob_property ("positions", ly_interval2scm (pos));
819 return SCM_UNSPECIFIED;
824 We can't combine with previous function, since check concave and
825 slope damping comes first.
827 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
829 Beam::shift_region_to_valid (SCM grob)
831 Grob *me = unsmob_grob (grob);
835 Array<Real> x_posns ;
836 Link_array<Item> stems=
837 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
838 Grob *common = stems[0];
839 for (int i=1; i < stems.size (); i++)
840 common = stems[i]->common_refpoint (common, X_AXIS);
842 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
843 for (int i=0; i < stems.size (); i++)
847 Real x = s->relative_coordinate (common, X_AXIS) - x0;
850 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
852 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
853 Real dy = pos.delta();
859 Shift the positions so that we have a chance of finding good
860 quants (i.e. no short stem failures.)
862 Interval feasible_left_point;
863 feasible_left_point.set_full ();
864 for (int i=0; i < stems.size (); i++)
867 if (Stem::invisible_b (s))
871 Direction d = Stem::get_direction (s);
875 TODO: use real beam space function
877 Real left_y = Stem::calc_stem_info (s).shortest_y_
878 - dydx * (x_posns [i] - x0);
884 feasible_left_point.intersect (flp);
887 if (feasible_left_point.empty_b())
889 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
891 else if (!feasible_left_point.elem_b(y))
893 if (isinf (feasible_left_point[DOWN]))
894 y = feasible_left_point[UP] - REGION_SIZE;
895 else if (isinf (feasible_left_point[UP]))
896 y = feasible_left_point[DOWN]+ REGION_SIZE;
898 y = feasible_left_point.center ();
900 pos = Interval (y, (y+dy));
901 me->set_grob_property ("positions", ly_interval2scm (pos));
902 return SCM_UNSPECIFIED;
906 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
908 Beam::check_concave (SCM smob)
910 Grob *me = unsmob_grob (smob);
912 Link_array<Item> stems =
913 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
915 for (int i = 0; i < stems.size ();)
917 if (Stem::invisible_b (stems[i]))
923 if (stems.size () < 3)
924 return SCM_UNSPECIFIED;
927 /* Concaveness #1: If distance of an inner notehead to line between
928 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
929 beam is concave (Heinz Stolba).
931 In the case of knees, the line connecting outer heads is often
932 not related to the beam slope (it may even go in the other
933 direction). Skip the check when the outer stems point in
934 different directions. --hwn
937 bool concaveness1 = false;
938 SCM gap = me->get_grob_property ("concaveness-gap");
939 if (gh_number_p (gap)
940 && Stem::get_direction(stems.top ())
941 == Stem::get_direction(stems[0]))
943 Real r1 = gh_scm2double (gap);
944 Real dy = Stem::chord_start_y (stems.top ())
945 - Stem::chord_start_y (stems[0]);
948 Real slope = dy / (stems.size () - 1);
950 Real y0 = Stem::chord_start_y (stems[0]);
951 for (int i = 1; i < stems.size () - 1; i++)
953 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
963 /* Concaveness #2: Sum distances of inner noteheads that fall
964 outside the interval of the two outer noteheads.
966 We only do this for beams where first and last stem have the same
970 Note that "convex" stems compensate for "concave" stems.
971 (is that intentional?) --hwn.
974 Real concaveness2 = 0;
975 SCM thresh = me->get_grob_property ("concaveness-threshold");
976 Real r2 = infinity_f;
977 if (!concaveness1 && gh_number_p (thresh)
978 && Stem::get_direction(stems.top ())
979 == Stem::get_direction(stems[0]))
981 r2 = gh_scm2double (thresh);
983 Direction dir = Stem::get_direction(stems.top ());
985 Interval iv (Stem::chord_start_y (stems[0]),
986 Stem::chord_start_y (stems.top ()));
988 if (iv[MAX] < iv[MIN])
991 for (int i = 1; i < stems.size () - 1; i++)
993 Real f = Stem::chord_start_y (stems[i]);
994 concave += ((f - iv[MAX] ) >? 0) +
995 ((f - iv[MIN] ) <? 0);
998 concaveness2 = concave / (stems.size () - 2);
1000 /* ugh: this is the a kludge to get
1001 input/regression/beam-concave.ly to behave as
1005 huh? we're dividing twice (which is not scalable) meaning that
1006 the longer the beam, the more unlikely it will be
1007 concave. Maybe you would even expect the other way around??
1012 concaveness2 /= (stems.size () - 2);
1015 /* TODO: some sort of damping iso -> plain horizontal */
1016 if (concaveness1 || concaveness2 > r2)
1018 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1019 Real r = pos.linear_combination (0);
1020 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1021 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1024 return SCM_UNSPECIFIED;
1027 /* This neat trick is by Werner Lemberg,
1028 damped = tanh (slope)
1029 corresponds with some tables in [Wanske] CHECKME */
1030 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1032 Beam::slope_damping (SCM smob)
1034 Grob *me = unsmob_grob (smob);
1036 if (visible_stem_count (me) <= 1)
1037 return SCM_UNSPECIFIED;
1039 SCM s = me->get_grob_property ("damping");
1040 int damping = gh_scm2int (s);
1044 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1045 Real dy = pos.delta ();
1047 // ugh -> use commonx
1048 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
1049 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1050 Real dydx = dy && dx ? dy/dx : 0;
1051 dydx = 0.6 * tanh (dydx) / damping;
1053 Real damped_dy = dydx * dx;
1054 pos[LEFT] += (dy - damped_dy) / 2;
1055 pos[RIGHT] -= (dy - damped_dy) / 2;
1057 me->set_grob_property ("positions", ly_interval2scm (pos));
1059 return SCM_UNSPECIFIED;
1063 Calculate the Y position of the stem-end, given the Y-left, Y-right
1064 in POS, and for stem S.
1066 If CORRECT, correct for multiplicity of beam in case of knees.
1069 TODO: junk CORRECT from this.
1072 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool correct)
1074 int beam_multiplicity = get_multiplicity (me);
1075 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
1078 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1079 Real interbeam = get_interbeam (me);
1081 // ugh -> use commonx
1082 Grob * fvs = first_visible_stem (me);
1083 Grob *lvs = last_visible_stem (me);
1085 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1086 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1087 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1088 Real dy = pos.delta ();
1089 Real stem_y = (dy && dx
1094 Direction my_dir = Directional_element_interface::get (s);
1095 Direction first_dir = fvs? Directional_element_interface::get (fvs) : my_dir;
1097 if (correct && my_dir != first_dir)
1100 WTF is happening here ?
1102 It looks as if this is some kind of fixup for multiple kneed
1103 beams to get a piece of stem at the #.
1114 Rules for this kind of stuff are hairy. In any event, the
1115 current stem should look at the multiplicity of its
1122 // FIXME, hairy stuff
1123 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1125 // huh, why not for first visible?
1128 What the heck is happening here??
1130 Grob *last_visible = last_visible_stem (me);
1133 if ( Staff_symbol_referencer::staff_symbol_l (s)
1134 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1135 stem_y += Directional_element_interface::get (me)
1136 * (beam_multiplicity - stem_multiplicity) * interbeam;
1139 programming_error ("No last visible stem");
1145 Hmm. At this time, beam position and slope are determined. Maybe,
1146 stem directions and length should set to relative to the chord's
1147 position of the beam. */
1149 Beam::set_stem_lengths (Grob *me)
1151 Link_array<Item> stems=
1152 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1154 if (stems.size () <= 1)
1157 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1158 for (int i=1; i < stems.size (); i++)
1159 if (!Stem::invisible_b (stems[i]))
1160 common = common->common_refpoint (stems[i], Y_AXIS);
1162 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1163 Real staff_space = Staff_symbol_referencer::staff_space (me);
1169 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1170 Direction dir = Directional_element_interface::get (me);
1171 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1174 for (int i=0; i < stems.size (); i++)
1177 if (Stem::invisible_b (s))
1180 Real stem_y = calc_stem_y (me, s, pos, true);
1183 // doesn't play well with dvips
1185 if (Stem::get_direction (s) == dir)
1186 stem_y += Stem::get_direction (s) * thick / 2;
1189 /* caution: stem measures in staff-positions */
1190 Real id = me->relative_coordinate (common, Y_AXIS)
1191 - stems[i]->relative_coordinate (common, Y_AXIS);
1192 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1197 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1199 Link_array<Grob> stems=
1200 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1203 for (int i=0; i < stems.size (); i++)
1207 /* Don't overwrite user override (?) */
1208 if (Stem::beam_count (stems[i], d) == -1
1209 /* Don't set beaming for outside of outer stems */
1210 && ! (d == LEFT && i == 0)
1211 && ! (d == RIGHT && i == stems.size () -1))
1213 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1214 Stem::set_beaming (stems[i], b, d);
1217 while (flip (&d) != LEFT);
1224 beams to go with one stem.
1228 The beam should be constructed by one function that knows where the
1229 X and Y points are, and only inspects the stems to obtain
1230 multiplicity and stem directions.
1234 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1236 // ugh -> use commonx
1238 && ! (next->relative_coordinate (0, X_AXIS)
1239 > here->relative_coordinate (0, X_AXIS)))
1241 && ! (prev->relative_coordinate (0, X_AXIS)
1242 < here->relative_coordinate (0, X_AXIS))))
1243 programming_error ("Beams are not left-to-right");
1245 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1246 Real bdy = get_interbeam (me);
1249 Molecule rightbeams;
1252 if (!Stem::first_head (here))
1256 int t = Stem::duration_log (here);
1258 SCM proc = me->get_grob_property ("flag-width-function");
1259 SCM result = gh_call1 (proc, gh_int2scm (t));
1260 nw_f = gh_scm2double (result);
1264 /* [Tremolo] beams on whole notes may not have direction set? */
1265 Direction dir = Directional_element_interface::get (here);
1267 /* half beams extending to the left. */
1270 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1271 - Stem::beam_count (prev, RIGHT);
1272 int lwholebeams= Stem::beam_count (here, LEFT)
1273 <? Stem::beam_count (prev, RIGHT);
1275 /* Half beam should be one note-width,
1276 but let's make sure two half-beams never touch */
1278 // FIXME: TODO (check) stem width / sloped beams
1279 Real w = here->relative_coordinate (0, X_AXIS)
1280 - prev->relative_coordinate (0, X_AXIS);
1281 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1283 * me->paper_l ()->get_var ("linethickness");
1287 if (lhalfs) // generates warnings if not
1288 a = Lookup::beam (dydx, w + stem_w, thick);
1289 a.translate (Offset (-w, -w * dydx));
1290 a.translate_axis (-stem_w/2, X_AXIS);
1291 for (int j = 0; j < lhalfs; j++)
1294 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1295 leftbeams.add_molecule (b);
1301 int rhalfs = Stem::beam_count (here, RIGHT)
1302 - Stem::beam_count (next, LEFT);
1303 int rwholebeams= Stem::beam_count (here, RIGHT)
1304 <? Stem::beam_count (next, LEFT);
1306 Real w = next->relative_coordinate (0, X_AXIS)
1307 - here->relative_coordinate (0, X_AXIS);
1309 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1311 * me->paper_l ()->get_var ("linethickness");
1313 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1314 a.translate_axis (- stem_w/2, X_AXIS);
1318 SCM gap = me->get_grob_property ("gap");
1319 if (gh_number_p (gap))
1321 int gap_i = gh_scm2int ((gap));
1322 int nogap = rwholebeams - gap_i;
1324 for (; j < nogap; j++)
1327 b.translate_axis (-dir * bdy * j, Y_AXIS);
1328 rightbeams.add_molecule (b);
1330 if (Stem::invisible_b (here))
1335 a = Lookup::beam (dydx, w + stem_w, thick);
1338 for (; j < rwholebeams; j++)
1342 if (Stem::invisible_b (here))
1343 // ugh, see chord-tremolo.ly
1344 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1347 b.translate (Offset (tx, -dir * bdy * j));
1348 rightbeams.add_molecule (b);
1353 a = Lookup::beam (dydx, w, thick);
1355 for (; j < rwholebeams + rhalfs; j++)
1358 b.translate_axis (- dir * bdy * j, Y_AXIS);
1359 rightbeams.add_molecule (b);
1363 leftbeams.add_molecule (rightbeams);
1369 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1371 Beam::brew_molecule (SCM smob)
1373 Grob *me =unsmob_grob (smob);
1376 if (!gh_pair_p (me->get_grob_property ("stems")))
1379 Link_array<Item>stems =
1380 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1381 if (visible_stem_count (me))
1383 // ugh -> use commonx
1384 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1385 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1389 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1390 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1393 SCM posns = me->get_grob_property ("positions");
1395 if (!ly_number_pair_p (posns))
1397 programming_error ("No beam posns");
1398 pos = Interval (0,0);
1401 pos= ly_scm2interval (posns);
1402 Real dy = pos.delta ();
1403 Real dydx = dy && dx ? dy/dx : 0;
1406 for (int i=0; i < stems.size (); i++)
1408 Item *item = stems[i];
1409 Item *prev = (i > 0)? stems[i-1] : 0;
1410 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1414 Molecule sb = stem_beams (me, item, next, prev, dydx);
1415 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1416 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1419 mol.add_molecule (sb);
1422 mol.translate_axis (x0
1423 - dynamic_cast<Spanner*> (me)
1424 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1427 #if (DEBUG_QUANTING)
1430 This code prints the demerits for each beam. Perhaps this
1431 should be switchable for those who want to twiddle with the
1437 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1440 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1443 SCM properties = Font_interface::font_alist_chain (me);
1446 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1447 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1451 return mol.smobbed_copy ();
1455 Beam::forced_stem_count (Grob *me)
1457 Link_array<Item>stems =
1458 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1460 for (int i=0; i < stems.size (); i++)
1464 if (Stem::invisible_b (s))
1467 if (((int)Stem::chord_start_y (s))
1468 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1478 Beam::visible_stem_count (Grob *me)
1480 Link_array<Item>stems =
1481 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1483 for (int i = stems.size (); i--;)
1485 if (!Stem::invisible_b (stems[i]))
1492 Beam::first_visible_stem (Grob *me)
1494 Link_array<Item>stems =
1495 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1497 for (int i = 0; i < stems.size (); i++)
1499 if (!Stem::invisible_b (stems[i]))
1506 Beam::last_visible_stem (Grob *me)
1508 Link_array<Item>stems =
1509 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1510 for (int i = stems.size (); i--;)
1512 if (!Stem::invisible_b (stems[i]))
1522 handle rest under beam (do_post: beams are calculated now)
1523 what about combination of collisions and rest under beam.
1527 rest -> stem -> beam -> interpolate_y_position ()
1529 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1531 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1533 Grob *rest = unsmob_grob (element_smob);
1534 Axis a = (Axis) gh_scm2int (axis);
1536 assert (a == Y_AXIS);
1538 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1541 return gh_double2scm (0.0);
1542 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1544 || !Beam::has_interface (beam)
1545 || !Beam::visible_stem_count (beam))
1546 return gh_double2scm (0.0);
1548 // make callback for rest from this.
1549 // todo: make sure this calced already.
1551 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1552 Interval pos (0, 0);
1553 SCM s = beam->get_grob_property ("positions");
1554 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1555 pos = ly_scm2interval (s);
1557 Real dy = pos.delta ();
1558 // ugh -> use commonx
1559 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1560 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1561 Real dydx = dy && dx ? dy/dx : 0;
1563 Direction d = Stem::get_direction (stem);
1564 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1566 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1569 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1572 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1574 minimum_dist + -d * (beamy - rest_dim) >? 0;
1576 int stafflines = Staff_symbol_referencer::line_count (rest);
1578 // move discretely by half spaces.
1579 int discrete_dist = int (ceil (dist));
1581 // move by whole spaces inside the staff.
1582 if (discrete_dist < stafflines+1)
1583 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1585 return gh_double2scm (-d * discrete_dist);
1591 ADD_INTERFACE (Beam, "beam-interface",
1594 #'thickness= weight of beams, in staffspace
1597 We take the least squares line through the ideal-length stems, and
1598 then damp that using
1600 damped = tanh (slope)
1602 this gives an unquantized left and right position for the beam end.
1603 Then we take all combinations of quantings near these left and right
1604 positions, and give them a score (according to how close they are to
1605 the ideal slope, how close the result is to the ideal stems, etc.). We
1606 take the best scoring combination.
1609 "position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy damping flag-width-function neutral-direction positions space-function thickness");