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
353 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
355 Beam::after_line_breaking (SCM smob)
357 Grob *me = unsmob_grob (smob);
359 /* Copy to mutable list. */
360 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
361 me->set_grob_property ("positions", s);
363 if (ly_car (s) != SCM_BOOL_F)
364 return SCM_UNSPECIFIED;
366 // one wonders if such genericity is necessary --hwn.
367 SCM callbacks = me->get_grob_property ("position-callbacks");
368 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
369 gh_call1 (ly_car (i), smob);
371 set_stem_lengths (me);
372 return SCM_UNSPECIFIED;
386 - Make all demerits customisable
388 - One sensible check per demerit (what's this --hwn)
390 - Add demerits for quants per se, as to forbid a specific quant
394 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
396 Beam::quanting (SCM smob)
398 Grob *me = unsmob_grob (smob);
400 SCM s = me->get_grob_property ("positions");
401 Real yl = gh_scm2double (gh_car (s));
402 Real yr = gh_scm2double (gh_cdr (s));
404 Real ss = Staff_symbol_referencer::staff_space (me);
405 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
406 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
409 SCM sdy = me->get_grob_property ("least-squares-dy");
410 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
413 Real sit = (thickness - slt) / 2;
415 Real hang = 1.0 - (thickness - slt) / 2;
416 Real quants [] = {straddle, sit, inter, hang };
418 int num_quants = int (sizeof (quants)/sizeof (Real));
423 going to REGION_SIZE == 2, yields another 0.6 second with
427 (result indexes between 70 and 575) ? --hwn.
434 Do stem computations. These depend on YL and YR linearly, so we can
435 precompute for every stem 2 factors.
437 Link_array<Grob> stems=
438 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
439 Array<Stem_info> stem_infos;
440 Array<Real> lbase_lengths;
441 Array<Real> rbase_lengths;
443 Drul_array<bool> dirs_found(0,0);
444 for (int i= 0; i < stems.size(); i++)
447 stem_infos.push (Stem::calc_stem_info (s));
448 dirs_found[stem_infos.top ().dir_] = true;
450 Real b = calc_stem_y (me, s, Interval (1,0), false);
451 lbase_lengths.push (b);
453 Real a = calc_stem_y (me, s, Interval (0,1), false);
454 rbase_lengths.push (a);
457 Direction ldir = Direction (stem_infos[0].dir_);
458 Direction rdir = Direction (stem_infos.top ().dir_);
459 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
462 int region_size = REGION_SIZE;
464 Knees are harder, lets try some more possibilities for knees.
469 for (int i = -region_size ; i < region_size; i++)
470 for (int j = 0; j < num_quants; j++)
472 quantsl.push (i + quants[j] + int (yl));
473 quantsr.push (i + quants[j] + int (yr));
476 Array<Quant_score> qscores;
478 for (int l =0; l < quantsl.size (); l++)
479 for (int r =0; r < quantsr.size (); r++)
491 This is a longish function, but we don't separate this out into
492 neat modular separate subfunctions, as the subfunctions would be
493 called for many values of YL, YR. By precomputing various
494 parameters outside of the loop, we can save a lot of time.
497 for (int i = qscores.size (); i--;)
498 if (qscores[i].demerits < 100)
501 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
505 Real rad = Staff_symbol_referencer::staff_radius (me);
506 int multiplicity = get_multiplicity (me);
507 Real interbeam = multiplicity < 4
508 ? (2*ss + slt - thickness) / 2.0
509 : (3*ss + slt - thickness) / 3.0;
511 for (int i = qscores.size (); i--;)
512 if (qscores[i].demerits < 100)
515 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
516 rad, slt, thickness, interbeam,
517 multiplicity, ldir, rdir);
521 for (int i = qscores.size (); i--;)
522 if (qscores[i].demerits < 100)
525 += score_stem_lengths (stems, stem_infos,
526 lbase_lengths, rbase_lengths,
528 me, qscores[i].yl, qscores[i].yr);
534 for (int i = qscores.size (); i--;)
536 if (qscores[i].demerits < best)
538 best = qscores [i].demerits ;
544 me->set_grob_property ("positions",
545 gh_cons (gh_double2scm (qscores[best_idx].yl),
546 gh_double2scm (qscores[best_idx].yr))
552 me->set_grob_property ("quant-score",
553 gh_double2scm (qscores[best_idx].demerits));
554 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
557 return SCM_UNSPECIFIED;
561 Beam::score_stem_lengths (Link_array<Grob>stems,
562 Array<Stem_info> stem_infos,
563 Array<Real> left_factor,
564 Array<Real> right_factor,
569 Real demerit_score = 0.0 ;
570 Real pen = STEM_LENGTH_LIMIT_PENALTY;
572 for (int i=0; i < stems.size (); i++)
575 if (Stem::invisible_b (s))
579 yl * left_factor[i] + right_factor[i]* yr;
581 Stem_info info = stem_infos[i];
582 Direction d = info.dir_;
585 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
587 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
588 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
591 demerit_score *= 2.0 / stems.size ();
593 return demerit_score;
597 Beam::score_slopes_dy (Grob *me,
599 Real dy_mus, Real dy_damp)
604 if (sign (dy_damp) != sign (dy))
606 dem += DAMPING_DIRECTIION_PENALTY;
609 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
610 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
618 return x - floor (x);
622 Beam::score_forbidden_quants (Grob*me,
626 Real thickness, Real interbeam,
628 Direction ldir, Direction rdir)
633 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
634 dem += INTER_QUANT_PENALTY;
635 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
636 dem += INTER_QUANT_PENALTY;
638 // todo: use multiplicity of outer stems.
639 if (multiplicity >= 2)
643 Real sit = (thickness - slt) / 2;
645 Real hang = 1.0 - (thickness - slt) / 2;
648 if (fabs (yl - ldir * interbeam) < rad
649 && fabs (my_modf (yl) - inter) < 1e-3)
650 dem += SECONDARY_BEAM_DEMERIT;
651 if (fabs (yr - rdir * interbeam) < rad
652 && fabs (my_modf (yr) - inter) < 1e-3)
653 dem += SECONDARY_BEAM_DEMERIT;
658 Can't we simply compute the distance between the nearest
659 staffline and the secondary beam? That would get rid of the
660 silly case analysis here (which is probably not when we have
661 different beam-thicknesses.)
667 // hmm, without Interval/Drul_array, you get ~ 4x same code...
668 if (fabs (yl - ldir * interbeam) < rad + inter)
670 if (ldir == UP && dy <= eps
671 && fabs (my_modf (yl) - sit) < eps)
672 dem += SECONDARY_BEAM_DEMERIT;
674 if (ldir == DOWN && dy >= eps
675 && fabs (my_modf (yl) - hang) < eps)
676 dem += SECONDARY_BEAM_DEMERIT;
679 if (fabs (yr - rdir * interbeam) < rad + inter)
681 if (rdir == UP && dy >= eps
682 && fabs (my_modf (yr) - sit) < eps)
683 dem += SECONDARY_BEAM_DEMERIT;
685 if (rdir == DOWN && dy <= eps
686 && fabs (my_modf (yr) - hang) < eps)
687 dem += SECONDARY_BEAM_DEMERIT;
690 if (multiplicity >= 3)
692 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
694 if (ldir == UP && dy <= eps
695 && fabs (my_modf (yl) - straddle) < eps)
696 dem += SECONDARY_BEAM_DEMERIT;
698 if (ldir == DOWN && dy >= eps
699 && fabs (my_modf (yl) - straddle) < eps)
700 dem += SECONDARY_BEAM_DEMERIT;
703 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
705 if (rdir == UP && dy >= eps
706 && fabs (my_modf (yr) - straddle) < eps)
707 dem += SECONDARY_BEAM_DEMERIT;
709 if (rdir == DOWN && dy <= eps
710 && fabs (my_modf (yr) - straddle) < eps)
711 dem += SECONDARY_BEAM_DEMERIT;
721 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
723 Beam::least_squares (SCM smob)
725 Grob *me = unsmob_grob (smob);
727 int count = visible_stem_count (me);
732 me->set_grob_property ("positions", ly_interval2scm (pos));
733 return SCM_UNSPECIFIED;
736 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
737 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
741 Array<Real> x_posns ;
742 Link_array<Item> stems=
743 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
744 Grob *common = stems[0];
745 for (int i=1; i < stems.size (); i++)
746 common = stems[i]->common_refpoint (common, X_AXIS);
748 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
749 for (int i=0; i < stems.size (); i++)
753 Real x = s->relative_coordinate (common, X_AXIS) - x0;
756 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
764 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
765 Stem::chord_start_y (last_visible_stem (me)));
769 TODO -- use scoring for this.
771 complicated, because we take stem-info.ideal for determining
774 /* Make simple beam on middle line have small tilt */
775 if (!ideal[LEFT] && chord.delta () && count == 2)
781 Direction d = (Direction) (sign (chord.delta ()) * UP);
782 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
797 Array<Offset> ideals;
798 for (int i=0; i < stems.size (); i++)
801 if (Stem::invisible_b (s))
803 ideals.push (Offset (x_posns[i],
804 Stem::calc_stem_info (s).ideal_y_));
806 minimise_least_squares (&dydx, &y, ideals);
809 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
810 pos = Interval (y, (y+dy));
813 me->set_grob_property ("positions", ly_interval2scm (pos));
815 return SCM_UNSPECIFIED;
820 We can't combine with previous function, since check concave and
821 slope damping comes first.
823 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
825 Beam::shift_region_to_valid (SCM grob)
827 Grob *me = unsmob_grob (grob);
831 Array<Real> x_posns ;
832 Link_array<Item> stems=
833 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
834 Grob *common = stems[0];
835 for (int i=1; i < stems.size (); i++)
836 common = stems[i]->common_refpoint (common, X_AXIS);
838 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
839 for (int i=0; i < stems.size (); i++)
843 Real x = s->relative_coordinate (common, X_AXIS) - x0;
846 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
848 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
849 Real dy = pos.delta();
855 Shift the positions so that we have a chance of finding good
856 quants (i.e. no short stem failures.)
858 Interval feasible_left_point;
859 feasible_left_point.set_full ();
860 for (int i=0; i < stems.size (); i++)
863 if (Stem::invisible_b (s))
867 Direction d = Stem::get_direction (s);
870 Real left_y = Stem::calc_stem_info (s).shortest_y_
871 - dydx * x_posns [i];
877 feasible_left_point.intersect (flp);
880 if (feasible_left_point.empty_b())
882 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
884 else if (!feasible_left_point.elem_b(y))
886 if (isinf (feasible_left_point[DOWN]))
887 y = feasible_left_point[UP] - REGION_SIZE;
888 else if (isinf (feasible_left_point[UP]))
889 y = feasible_left_point[DOWN]+ REGION_SIZE;
891 y = feasible_left_point.center ();
893 pos = Interval (y, (y+dy));
894 me->set_grob_property ("positions", ly_interval2scm (pos));
895 return SCM_UNSPECIFIED;
899 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
901 Beam::check_concave (SCM smob)
903 Grob *me = unsmob_grob (smob);
905 Link_array<Item> stems =
906 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
908 for (int i = 0; i < stems.size ();)
910 if (Stem::invisible_b (stems[i]))
916 if (stems.size () < 3)
917 return SCM_UNSPECIFIED;
920 /* Concaveness #1: If distance of an inner notehead to line between
921 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
922 beam is concave (Heinz Stolba).
924 In the case of knees, the line connecting outer heads is often
925 not related to the beam slope (it may even go in the other
926 direction). Skip the check when the outer stems point in
927 different directions. --hwn
930 bool concaveness1 = false;
931 SCM gap = me->get_grob_property ("concaveness-gap");
932 if (gh_number_p (gap)
933 && Stem::get_direction(stems.top ())
934 == Stem::get_direction(stems[0]))
936 Real r1 = gh_scm2double (gap);
937 Real dy = Stem::chord_start_y (stems.top ())
938 - Stem::chord_start_y (stems[0]);
941 Real slope = dy / (stems.size () - 1);
943 Real y0 = Stem::chord_start_y (stems[0]);
944 for (int i = 1; i < stems.size () - 1; i++)
946 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
956 /* Concaveness #2: Sum distances of inner noteheads that fall
957 outside the interval of the two outer noteheads.
959 We only do this for beams where first and last stem have the same
963 Note that "convex" stems compensate for "concave" stems.
964 (is that intentional?) --hwn.
967 Real concaveness2 = 0;
968 SCM thresh = me->get_grob_property ("concaveness-threshold");
969 Real r2 = infinity_f;
970 if (!concaveness1 && gh_number_p (thresh)
971 && Stem::get_direction(stems.top ())
972 == Stem::get_direction(stems[0]))
974 r2 = gh_scm2double (thresh);
976 Direction dir = Stem::get_direction(stems.top ());
978 Interval iv (Stem::chord_start_y (stems[0]),
979 Stem::chord_start_y (stems.top ()));
981 if (iv[MAX] < iv[MIN])
984 for (int i = 1; i < stems.size () - 1; i++)
986 Real f = Stem::chord_start_y (stems[i]);
987 concave += ((f - iv[MAX] ) >? 0) +
988 ((f - iv[MIN] ) <? 0);
991 concaveness2 = concave / (stems.size () - 2);
993 /* ugh: this is the a kludge to get
994 input/regression/beam-concave.ly to behave as
998 huh? we're dividing twice (which is not scalable) meaning that
999 the longer the beam, the more unlikely it will be
1000 concave. Maybe you would even expect the other way around??
1005 concaveness2 /= (stems.size () - 2);
1008 /* TODO: some sort of damping iso -> plain horizontal */
1009 if (concaveness1 || concaveness2 > r2)
1011 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1012 Real r = pos.linear_combination (0);
1013 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1014 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1017 return SCM_UNSPECIFIED;
1020 /* This neat trick is by Werner Lemberg,
1021 damped = tanh (slope)
1022 corresponds with some tables in [Wanske] CHECKME */
1023 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1025 Beam::slope_damping (SCM smob)
1027 Grob *me = unsmob_grob (smob);
1029 if (visible_stem_count (me) <= 1)
1030 return SCM_UNSPECIFIED;
1032 SCM s = me->get_grob_property ("damping");
1033 int damping = gh_scm2int (s);
1037 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1038 Real dy = pos.delta ();
1040 // ugh -> use commonx
1041 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
1042 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1043 Real dydx = dy && dx ? dy/dx : 0;
1044 dydx = 0.6 * tanh (dydx) / damping;
1046 Real damped_dy = dydx * dx;
1047 pos[LEFT] += (dy - damped_dy) / 2;
1048 pos[RIGHT] -= (dy - damped_dy) / 2;
1050 me->set_grob_property ("positions", ly_interval2scm (pos));
1052 return SCM_UNSPECIFIED;
1056 Calculate the Y position of the stem-end, given the Y-left, Y-right
1057 in POS, and for stem S.
1059 If CORRECT, correct for multiplicity of beam in case of knees.
1062 TODO: junk CORRECT from this.
1065 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool correct)
1067 int beam_multiplicity = get_multiplicity (me);
1068 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
1071 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1072 Real interbeam = get_interbeam (me);
1074 // ugh -> use commonx
1075 Grob * fvs = first_visible_stem (me);
1076 Grob *lvs = last_visible_stem (me);
1078 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1079 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1080 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1081 Real dy = pos.delta ();
1082 Real stem_y = (dy && dx
1087 Direction my_dir = Directional_element_interface::get (s);
1088 Direction first_dir = fvs? Directional_element_interface::get (fvs) : my_dir;
1090 if (correct && my_dir != first_dir)
1093 WTF is happening here ?
1095 It looks as if this is some kind of fixup for multiple kneed
1096 beams to get a piece of stem at the #.
1107 Rules for this kind of stuff are hairy. In any event, the
1108 current stem should look at the multiplicity of its
1115 // FIXME, hairy stuff
1116 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1118 // huh, why not for first visible?
1121 What the heck is happening here??
1123 Grob *last_visible = last_visible_stem (me);
1126 if ( Staff_symbol_referencer::staff_symbol_l (s)
1127 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1128 stem_y += Directional_element_interface::get (me)
1129 * (beam_multiplicity - stem_multiplicity) * interbeam;
1132 programming_error ("No last visible stem");
1138 Hmm. At this time, beam position and slope are determined. Maybe,
1139 stem directions and length should set to relative to the chord's
1140 position of the beam. */
1142 Beam::set_stem_lengths (Grob *me)
1144 Link_array<Item> stems=
1145 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1147 if (stems.size () <= 1)
1150 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1151 for (int i=1; i < stems.size (); i++)
1152 if (!Stem::invisible_b (stems[i]))
1153 common = common->common_refpoint (stems[i], Y_AXIS);
1155 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1156 Real staff_space = Staff_symbol_referencer::staff_space (me);
1162 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1163 Direction dir = Directional_element_interface::get (me);
1164 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1167 for (int i=0; i < stems.size (); i++)
1170 if (Stem::invisible_b (s))
1173 Real stem_y = calc_stem_y (me, s, pos, true);
1176 // doesn't play well with dvips
1178 if (Stem::get_direction (s) == dir)
1179 stem_y += Stem::get_direction (s) * thick / 2;
1182 /* caution: stem measures in staff-positions */
1183 Real id = me->relative_coordinate (common, Y_AXIS)
1184 - stems[i]->relative_coordinate (common, Y_AXIS);
1185 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1190 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1192 Link_array<Grob> stems=
1193 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1196 for (int i=0; i < stems.size (); i++)
1200 /* Don't overwrite user override (?) */
1201 if (Stem::beam_count (stems[i], d) == -1
1202 /* Don't set beaming for outside of outer stems */
1203 && ! (d == LEFT && i == 0)
1204 && ! (d == RIGHT && i == stems.size () -1))
1206 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1207 Stem::set_beaming (stems[i], b, d);
1210 while (flip (&d) != LEFT);
1217 beams to go with one stem.
1221 The beam should be constructed by one function that knows where the
1222 X and Y points are, and only inspects the stems to obtain
1223 multiplicity and stem directions.
1227 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1229 // ugh -> use commonx
1231 && ! (next->relative_coordinate (0, X_AXIS)
1232 > here->relative_coordinate (0, X_AXIS)))
1234 && ! (prev->relative_coordinate (0, X_AXIS)
1235 < here->relative_coordinate (0, X_AXIS))))
1236 programming_error ("Beams are not left-to-right");
1238 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1239 Real bdy = get_interbeam (me);
1242 Molecule rightbeams;
1245 if (!Stem::first_head (here))
1249 int t = Stem::duration_log (here);
1251 SCM proc = me->get_grob_property ("flag-width-function");
1252 SCM result = gh_call1 (proc, gh_int2scm (t));
1253 nw_f = gh_scm2double (result);
1257 /* [Tremolo] beams on whole notes may not have direction set? */
1258 Direction dir = Directional_element_interface::get (here);
1260 /* half beams extending to the left. */
1263 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1264 - Stem::beam_count (prev, RIGHT);
1265 int lwholebeams= Stem::beam_count (here, LEFT)
1266 <? Stem::beam_count (prev, RIGHT);
1268 /* Half beam should be one note-width,
1269 but let's make sure two half-beams never touch */
1271 // FIXME: TODO (check) stem width / sloped beams
1272 Real w = here->relative_coordinate (0, X_AXIS)
1273 - prev->relative_coordinate (0, X_AXIS);
1274 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1276 * me->paper_l ()->get_var ("linethickness");
1280 if (lhalfs) // generates warnings if not
1281 a = Lookup::beam (dydx, w + stem_w, thick);
1282 a.translate (Offset (-w, -w * dydx));
1283 a.translate_axis (-stem_w/2, X_AXIS);
1284 for (int j = 0; j < lhalfs; j++)
1287 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1288 leftbeams.add_molecule (b);
1294 int rhalfs = Stem::beam_count (here, RIGHT)
1295 - Stem::beam_count (next, LEFT);
1296 int rwholebeams= Stem::beam_count (here, RIGHT)
1297 <? Stem::beam_count (next, LEFT);
1299 Real w = next->relative_coordinate (0, X_AXIS)
1300 - here->relative_coordinate (0, X_AXIS);
1302 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1304 * me->paper_l ()->get_var ("linethickness");
1306 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1307 a.translate_axis (- stem_w/2, X_AXIS);
1311 SCM gap = me->get_grob_property ("gap");
1312 if (gh_number_p (gap))
1314 int gap_i = gh_scm2int ((gap));
1315 int nogap = rwholebeams - gap_i;
1317 for (; j < nogap; j++)
1320 b.translate_axis (-dir * bdy * j, Y_AXIS);
1321 rightbeams.add_molecule (b);
1323 if (Stem::invisible_b (here))
1328 a = Lookup::beam (dydx, w + stem_w, thick);
1331 for (; j < rwholebeams; j++)
1335 if (Stem::invisible_b (here))
1336 // ugh, see chord-tremolo.ly
1337 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1340 b.translate (Offset (tx, -dir * bdy * j));
1341 rightbeams.add_molecule (b);
1346 a = Lookup::beam (dydx, w, thick);
1348 for (; j < rwholebeams + rhalfs; j++)
1351 b.translate_axis (- dir * bdy * j, Y_AXIS);
1352 rightbeams.add_molecule (b);
1356 leftbeams.add_molecule (rightbeams);
1362 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1364 Beam::brew_molecule (SCM smob)
1366 Grob *me =unsmob_grob (smob);
1369 if (!gh_pair_p (me->get_grob_property ("stems")))
1372 Link_array<Item>stems =
1373 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1374 if (visible_stem_count (me))
1376 // ugh -> use commonx
1377 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1378 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1382 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1383 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1386 SCM posns = me->get_grob_property ("positions");
1388 if (!ly_number_pair_p (posns))
1390 programming_error ("No beam posns");
1391 pos = Interval (0,0);
1394 pos= ly_scm2interval (posns);
1395 Real dy = pos.delta ();
1396 Real dydx = dy && dx ? dy/dx : 0;
1399 for (int i=0; i < stems.size (); i++)
1401 Item *item = stems[i];
1402 Item *prev = (i > 0)? stems[i-1] : 0;
1403 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1407 Molecule sb = stem_beams (me, item, next, prev, dydx);
1408 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1409 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1412 mol.add_molecule (sb);
1415 mol.translate_axis (x0
1416 - dynamic_cast<Spanner*> (me)
1417 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1420 #if (DEBUG_QUANTING)
1423 This code prints the demerits for each beam. Perhaps this
1424 should be switchable for those who want to twiddle with the
1430 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1433 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1436 SCM properties = Font_interface::font_alist_chain (me);
1439 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1440 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1444 return mol.smobbed_copy ();
1448 Beam::forced_stem_count (Grob *me)
1450 Link_array<Item>stems =
1451 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1453 for (int i=0; i < stems.size (); i++)
1457 if (Stem::invisible_b (s))
1460 if (((int)Stem::chord_start_y (s))
1461 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1471 Beam::visible_stem_count (Grob *me)
1473 Link_array<Item>stems =
1474 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1476 for (int i = stems.size (); i--;)
1478 if (!Stem::invisible_b (stems[i]))
1485 Beam::first_visible_stem (Grob *me)
1487 Link_array<Item>stems =
1488 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1490 for (int i = 0; i < stems.size (); i++)
1492 if (!Stem::invisible_b (stems[i]))
1499 Beam::last_visible_stem (Grob *me)
1501 Link_array<Item>stems =
1502 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1503 for (int i = stems.size (); i--;)
1505 if (!Stem::invisible_b (stems[i]))
1515 handle rest under beam (do_post: beams are calculated now)
1516 what about combination of collisions and rest under beam.
1520 rest -> stem -> beam -> interpolate_y_position ()
1522 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1524 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1526 Grob *rest = unsmob_grob (element_smob);
1527 Axis a = (Axis) gh_scm2int (axis);
1529 assert (a == Y_AXIS);
1531 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1534 return gh_double2scm (0.0);
1535 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1537 || !Beam::has_interface (beam)
1538 || !Beam::visible_stem_count (beam))
1539 return gh_double2scm (0.0);
1541 // make callback for rest from this.
1542 // todo: make sure this calced already.
1544 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1545 Interval pos (0, 0);
1546 SCM s = beam->get_grob_property ("positions");
1547 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1548 pos = ly_scm2interval (s);
1550 Real dy = pos.delta ();
1551 // ugh -> use commonx
1552 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1553 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1554 Real dydx = dy && dx ? dy/dx : 0;
1556 Direction d = Stem::get_direction (stem);
1557 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1559 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1562 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1565 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1567 minimum_dist + -d * (beamy - rest_dim) >? 0;
1569 int stafflines = Staff_symbol_referencer::line_count (rest);
1571 // move discretely by half spaces.
1572 int discrete_dist = int (ceil (dist));
1574 // move by whole spaces inside the staff.
1575 if (discrete_dist < stafflines+1)
1576 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1578 return gh_double2scm (-d * discrete_dist);
1584 ADD_INTERFACE (Beam, "beam-interface",
1587 #'thickness= weight of beams, in staffspace
1590 We take the least squares line through the ideal-length stems, and
1591 then damp that using
1593 damped = tanh (slope)
1595 this gives an unquantized left and right position for the beam end.
1596 Then we take all combinations of quantings near these left and right
1597 positions, and give them a score (according to how close they are to
1598 the ideal slope, how close the result is to the ideal stems, etc.). We
1599 take the best scoring combination.
1602 "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");