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).
30 #include <math.h> // tanh.
32 #include "molecule.hh"
33 #include "directional-element-interface.hh"
37 #include "least-squares.hh"
39 #include "paper-def.hh"
41 #include "group-interface.hh"
42 #include "staff-symbol-referencer.hh"
48 #define DEBUG_QUANTING 0
52 #include "text-item.hh" // debug output.
53 #include "font-interface.hh" // debug output.
57 const int INTER_QUANT_PENALTY = 1000;
58 const int SECONDARY_BEAM_DEMERIT = 15;
59 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
60 const int STEM_LENGTH_LIMIT_PENALTY = 500;
61 const int DAMPING_DIRECTIION_PENALTY = 800;
62 const int MUSICAL_DIRECTION_FACTOR = 400;
63 const int IDEAL_SLOPE_FACTOR = 10;
67 shrink_extra_weight (Real x)
69 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
73 Beam::add_stem (Grob *me, Grob *s)
75 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
77 s->add_dependency (me);
79 assert (!Stem::beam_l (s));
80 s->set_grob_property ("beam", me->self_scm ());
82 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
86 Beam::get_interbeam (Grob *me)
88 SCM func = me->get_grob_property ("space-function");
89 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
90 return gh_scm2double (s);
97 Beam::get_multiplicity (Grob *me)
100 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
102 Grob *sc = unsmob_grob (ly_car (s));
104 if (Stem::has_interface (sc))
105 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
110 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
112 Beam::space_function (SCM smob, SCM multiplicity)
114 Grob *me = unsmob_grob (smob);
116 Real staff_space = Staff_symbol_referencer::staff_space (me);
117 Real line = me->paper_l ()->get_var ("linethickness");
118 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
121 Real interbeam = gh_scm2int (multiplicity) < 4
122 ? (2*staff_space + line - thickness) / 2.0
123 : (3*staff_space + line - thickness) / 3.0;
125 return gh_double2scm (interbeam);
129 /* After pre-processing all directions should be set.
130 Several post-processing routines (stem, slur, script) need stem/beam
132 Currenly, this means that beam has set all stem's directions.
133 [Alternatively, stems could set its own directions, according to
134 their beam, during 'final-pre-processing'.] */
135 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
137 Beam::before_line_breaking (SCM smob)
139 Grob *me = unsmob_grob (smob);
141 /* Beams with less than 2 two stems don't make much sense, but could happen
146 For a beam that only has one stem, we try to do some disappearance magic:
147 we revert the flag, and move on to The Eternal Engraving Fields. */
149 int count = visible_stem_count (me);
152 me->warning (_ ("beam has less than two visible stems"));
154 SCM stems = me->get_grob_property ("stems");
155 if (scm_ilength (stems) == 1)
157 me->warning (_ ("Beam has less than two stems. Removing beam."));
159 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
162 return SCM_UNSPECIFIED;
164 else if (scm_ilength (stems) == 0)
167 return SCM_UNSPECIFIED;
172 Direction d = get_default_dir (me);
174 consider_auto_knees (me, d);
175 set_stem_directions (me, d);
176 set_stem_shorten (me);
183 Beam::get_default_dir (Grob *me)
185 Drul_array<int> total;
186 total[UP] = total[DOWN] = 0;
187 Drul_array<int> count;
188 count[UP] = count[DOWN] = 0;
191 Link_array<Item> stems=
192 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
194 for (int i=0; i <stems.size (); i++)
197 Direction sd = Directional_element_interface::get (s);
199 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
200 int current = sd ? (1 + d * sd)/2 : center_distance;
207 } while (flip (&d) != DOWN);
209 SCM func = me->get_grob_property ("dir-function");
210 SCM s = gh_call2 (func,
211 gh_cons (gh_int2scm (count[UP]),
212 gh_int2scm (count[DOWN])),
213 gh_cons (gh_int2scm (total[UP]),
214 gh_int2scm (total[DOWN])));
216 if (gh_number_p (s) && gh_scm2int (s))
219 /* If dir is not determined: get default */
220 return to_dir (me->get_grob_property ("neutral-direction"));
224 /* Set all stems with non-forced direction to beam direction.
225 Urg: non-forced should become `without/with unforced' direction,
226 once stem gets cleaned-up. */
228 Beam::set_stem_directions (Grob *me, Direction d)
230 Link_array<Item> stems
231 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
233 for (int i=0; i <stems.size (); i++)
236 SCM force = s->remove_grob_property ("dir-forced");
237 if (!gh_boolean_p (force) || !gh_scm2bool (force))
238 Directional_element_interface::set (s, d);
242 /* Simplistic auto-knees; only consider vertical gap between two
245 `Forced' stem directions are ignored. If you don't want auto-knees,
246 don't set, or unset auto-knee-gap. */
248 Beam::consider_auto_knees (Grob *me, Direction d)
250 SCM scm = me->get_grob_property ("auto-knee-gap");
252 if (gh_number_p (scm))
256 Real staff_space = Staff_symbol_referencer::staff_space (me);
257 Real gap = gh_scm2double (scm) / staff_space;
260 Link_array<Item> stems=
261 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
263 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
264 for (int i=1; i < stems.size (); i++)
265 if (!Stem::invisible_b (stems[i]))
266 common = common->common_refpoint (stems[i], Y_AXIS);
269 for (int i=1; i < stems.size (); i++)
271 if (!Stem::invisible_b (stems[i-1]))
273 if (Stem::invisible_b (stems[l]))
275 if (Stem::invisible_b (stems[i]))
278 Real left = Stem::extremal_heads (stems[l])[d]
279 ->relative_coordinate (common, Y_AXIS);
280 Real right = Stem::extremal_heads (stems[i])[-d]
281 ->relative_coordinate (common, Y_AXIS);
283 Real dy = right - left;
287 knee_y = (right + left) / 2;
295 for (int i=0; i < stems.size (); i++)
297 if (Stem::invisible_b (stems[i]))
300 Real y = Stem::extremal_heads (stems[i])[d]
301 ->relative_coordinate (common, Y_AXIS);
303 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
304 s->set_grob_property ("dir-forced", SCM_BOOL_T);
310 /* Set stem's shorten property if unset.
313 take some y-position (chord/beam/nearest?) into account
314 scmify forced-fraction
318 why is shorten stored in beam, and not directly in stem?
322 Beam::set_stem_shorten (Grob *m)
324 Spanner*me = dynamic_cast<Spanner*> (m);
326 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
328 int multiplicity = get_multiplicity (me);
330 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
331 if (shorten == SCM_EOL)
334 int sz = scm_ilength (shorten);
336 Real staff_space = Staff_symbol_referencer::staff_space (me);
337 SCM shorten_elt = scm_list_ref (shorten,
338 gh_int2scm (multiplicity <? (sz - 1)));
339 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
341 /* your similar cute comment here */
342 shorten_f *= forced_fraction;
345 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
348 /* Call list of y-dy-callbacks, that handle setting of
349 grob-properties y, dy.
351 User may set grob-properties: y-position-hs and height-hs
352 (to be fixed) that override the calculated y and dy.
354 Because y and dy cannot be calculated and quanted separately, we
355 always calculate both, then check for user override. */
356 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
358 Beam::after_line_breaking (SCM smob)
360 Grob *me = unsmob_grob (smob);
362 /* Copy to mutable list. */
363 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
364 me->set_grob_property ("positions", s);
366 if (ly_car (s) != SCM_BOOL_F)
367 return SCM_UNSPECIFIED;
369 // one wonders if such genericity is necessary --hwn.
370 SCM callbacks = me->get_grob_property ("position-callbacks");
371 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
372 gh_call1 (ly_car (i), smob);
374 set_stem_lengths (me);
375 return SCM_UNSPECIFIED;
389 - Make all demerits customisable
391 - One sensible check per demerit (what's this --hwn)
393 - Add demerits for quants per se, as to forbid a specific quant
397 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
399 Beam::quanting (SCM smob)
401 Grob *me = unsmob_grob (smob);
403 SCM s = me->get_grob_property ("positions");
404 Real yl = gh_scm2double (gh_car (s));
405 Real yr = gh_scm2double (gh_cdr (s));
407 Real ss = Staff_symbol_referencer::staff_space (me);
408 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
409 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
412 SCM sdy = me->get_grob_property ("least-squares-dy");
413 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
416 Real sit = (thickness - slt) / 2;
418 Real hang = 1.0 - (thickness - slt) / 2;
419 Real quants [] = {straddle, sit, inter, hang };
421 int num_quants = int (sizeof (quants)/sizeof (Real));
426 going to REGION_SIZE == 2, yields another 0.6 second with
430 (result indexes between 70 and 575) ? --hwn.
437 Do stem computations. These depend on YL and YR linearly, so we can
438 precompute for every stem 2 factors.
440 Link_array<Grob> stems=
441 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
442 Array<Stem_info> stem_infos;
443 Array<Real> lbase_lengths;
444 Array<Real> rbase_lengths;
446 Drul_array<bool> dirs_found(0,0);
447 for (int i= 0; i < stems.size(); i++)
450 stem_infos.push( Stem::calc_stem_info (s));
452 Real b = calc_stem_y (me, s, Interval (1,0));
453 lbase_lengths.push (b);
455 b = calc_stem_y (me, s, Interval (0,1));
456 rbase_lengths.push (b);
458 dirs_found [stem_infos.top().dir_] = true;
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];
469 Knees are harder, lets try some more possibilities for knees.
474 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
475 for (int j = 0; j < num_quants; j++)
477 quantsl.push (i + quants[j] + int (yl));
478 quantsr.push (i + quants[j] + int (yr));
481 Array<Quant_score> qscores;
483 for (int l =0; l < quantsl.size (); l++)
484 for (int r =0; r < quantsr.size (); r++)
496 This is a longish function, but we don't separate this out into
497 neat modular separate subfunctions, as the subfunctions would be
498 called for many values of YL, YR. By precomputing various
499 parameters outside of the loop, we can save a lot of time.
502 for (int i = qscores.size (); i--;)
503 if (qscores[i].demerits < 100)
506 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
510 Real rad = Staff_symbol_referencer::staff_radius (me);
511 int multiplicity = get_multiplicity (me);
512 Real interbeam = multiplicity < 4
513 ? (2*ss + slt - thickness) / 2.0
514 : (3*ss + slt - thickness) / 3.0;
516 for (int i = qscores.size (); i--;)
517 if (qscores[i].demerits < 100)
520 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
521 rad, slt, thickness, interbeam,
522 multiplicity, ldir, rdir);
526 for (int i = qscores.size (); i--;)
527 if (qscores[i].demerits < 100)
530 += score_stem_lengths (stems, stem_infos,
531 lbase_lengths, rbase_lengths,
533 me, qscores[i].yl, qscores[i].yr);
539 for (int i = qscores.size (); i--;)
541 if (qscores[i].demerits < best)
543 best = qscores [i].demerits ;
549 me->set_grob_property ("positions",
550 gh_cons (gh_double2scm (qscores[best_idx].yl),
551 gh_double2scm (qscores[best_idx].yr))
557 me->set_grob_property ("quant-score",
558 gh_double2scm (qscores[best_idx].demerits));
559 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
562 return SCM_UNSPECIFIED;
566 Beam::score_stem_lengths (Link_array<Grob>stems,
567 Array<Stem_info> stem_infos,
568 Array<Real> left_factor,
569 Array<Real> right_factor,
574 Real demerit_score = 0.0 ;
575 Real pen = STEM_LENGTH_LIMIT_PENALTY;
579 for (int i=0; i < stems.size (); i++)
582 if (Stem::invisible_b (s))
586 yl * left_factor[i] + right_factor[i]* yr;
588 Stem_info info = stem_infos[i];
589 Direction d = info.dir_;
591 demerit_score += pen * ( 0 >? (info.dir_ *(info.shortest_y_ - current_y)));
592 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
593 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
596 demerit_score *= 2.0 / stems.size ();
598 return demerit_score;
602 Beam::score_slopes_dy (Grob *me,
604 Real dy_mus, Real dy_damp)
609 if (sign (dy_damp) != sign (dy))
611 dem += DAMPING_DIRECTIION_PENALTY;
614 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
615 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
623 return x - floor (x);
627 Beam::score_forbidden_quants (Grob*me,
631 Real thickness, Real interbeam,
633 Direction ldir, Direction rdir)
638 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
639 dem += INTER_QUANT_PENALTY;
640 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
641 dem += INTER_QUANT_PENALTY;
643 // todo: use multiplicity of outer stems.
644 if (multiplicity >= 2)
648 Real sit = (thickness - slt) / 2;
650 Real hang = 1.0 - (thickness - slt) / 2;
653 if (fabs (yl - ldir * interbeam) < rad
654 && fabs (my_modf (yl) - inter) < 1e-3)
655 dem += SECONDARY_BEAM_DEMERIT;
656 if (fabs (yr - rdir * interbeam) < rad
657 && fabs (my_modf (yr) - inter) < 1e-3)
658 dem += SECONDARY_BEAM_DEMERIT;
663 Can't we simply compute the distance between the nearest
664 staffline and the secondary beam? That would get rid of the
665 silly case analysis here (which is probably not when we have
666 different beam-thicknesses.)
672 // hmm, without Interval/Drul_array, you get ~ 4x same code...
673 if (fabs (yl - ldir * interbeam) < rad + inter)
675 if (ldir == UP && dy <= eps
676 && fabs (my_modf (yl) - sit) < eps)
677 dem += SECONDARY_BEAM_DEMERIT;
679 if (ldir == DOWN && dy >= eps
680 && fabs (my_modf (yl) - hang) < eps)
681 dem += SECONDARY_BEAM_DEMERIT;
684 if (fabs (yr - rdir * interbeam) < rad + inter)
686 if (rdir == UP && dy >= eps
687 && fabs (my_modf (yr) - sit) < eps)
688 dem += SECONDARY_BEAM_DEMERIT;
690 if (rdir == DOWN && dy <= eps
691 && fabs (my_modf (yr) - hang) < eps)
692 dem += SECONDARY_BEAM_DEMERIT;
695 if (multiplicity >= 3)
697 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
699 if (ldir == UP && dy <= eps
700 && fabs (my_modf (yl) - straddle) < eps)
701 dem += SECONDARY_BEAM_DEMERIT;
703 if (ldir == DOWN && dy >= eps
704 && fabs (my_modf (yl) - straddle) < eps)
705 dem += SECONDARY_BEAM_DEMERIT;
708 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
710 if (rdir == UP && dy >= eps
711 && fabs (my_modf (yr) - straddle) < eps)
712 dem += SECONDARY_BEAM_DEMERIT;
714 if (rdir == DOWN && dy <= eps
715 && fabs (my_modf (yr) - straddle) < eps)
716 dem += SECONDARY_BEAM_DEMERIT;
726 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
728 Beam::least_squares (SCM smob)
730 Grob *me = unsmob_grob (smob);
732 int count = visible_stem_count (me);
737 me->set_grob_property ("positions", ly_interval2scm (pos));
738 return SCM_UNSPECIFIED;
741 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
742 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
746 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
747 Stem::chord_start_y (last_visible_stem (me)));
751 TODO : use scoring for this.
753 complicated, because we take stem-info.ideal for determining
757 /* Make simple beam on middle line have small tilt */
758 if (!ideal[LEFT] && chord.delta () && count == 2)
764 Direction d = (Direction) (sign (chord.delta ()) * UP);
765 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
776 Array<Offset> ideals;
778 // ugh -> use commonx
779 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
780 Link_array<Item> stems=
781 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
783 for (int i=0; i < stems.size (); i++)
786 if (Stem::invisible_b (s))
788 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
789 Stem::calc_stem_info (s).ideal_y_));
793 minimise_least_squares (&dydx, &y, ideals);
795 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
797 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
799 pos = Interval (y, (y+dy));
802 me->set_grob_property ("positions", ly_interval2scm (pos));
803 return SCM_UNSPECIFIED;
806 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
808 Beam::check_concave (SCM smob)
810 Grob *me = unsmob_grob (smob);
812 Link_array<Item> stems =
813 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
815 for (int i = 0; i < stems.size ();)
817 if (Stem::invisible_b (stems[i]))
823 if (stems.size () < 3)
824 return SCM_UNSPECIFIED;
827 /* Concaveness #1: If distance of an inner notehead to line between
828 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
829 beam is concave (Heinz Stolba).
831 In the case of knees, the line connecting outer heads is often
832 not related to the beam slope (it may even go in the other
833 direction). Skip the check when the outer stems point in
834 different directions. --hwn
837 bool concaveness1 = false;
838 SCM gap = me->get_grob_property ("concaveness-gap");
839 if (gh_number_p (gap)
840 && Stem::get_direction(stems.top ())
841 == Stem::get_direction(stems[0]))
843 Real r1 = gh_scm2double (gap);
844 Real dy = Stem::chord_start_y (stems.top ())
845 - Stem::chord_start_y (stems[0]);
848 Real slope = dy / (stems.size () - 1);
850 Real y0 = Stem::chord_start_y (stems[0]);
851 for (int i = 1; i < stems.size () - 1; i++)
853 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
863 /* Concaveness #2: Sum distances of inner noteheads that fall
864 outside the interval of the two outer noteheads.
866 We only do this for beams where first and last stem have the same
870 Note that "convex" stems compensate for "concave" stems.
871 (is that intentional?) --hwn.
874 Real concaveness2 = 0;
875 SCM thresh = me->get_grob_property ("concaveness-threshold");
876 Real r2 = infinity_f;
877 if (!concaveness1 && gh_number_p (thresh)
878 && Stem::get_direction(stems.top ())
879 == Stem::get_direction(stems[0]))
881 r2 = gh_scm2double (thresh);
883 Direction dir = Stem::get_direction(stems.top ());
885 Interval iv (Stem::chord_start_y (stems[0]),
886 Stem::chord_start_y (stems.top ()));
888 if (iv[MAX] < iv[MIN])
891 for (int i = 1; i < stems.size () - 1; i++)
893 Real f = Stem::chord_start_y (stems[i]);
894 concave += ((f - iv[MAX] ) >? 0) +
895 ((f - iv[MIN] ) <? 0);
898 concaveness2 = concave / (stems.size () - 2);
900 /* ugh: this is the a kludge to get
901 input/regression/beam-concave.ly to behave as
905 huh? we're dividing twice (which is not scalable) meaning that
906 the longer the beam, the more unlikely it will be
907 concave. Maybe you would even expect the other way around??
912 concaveness2 /= (stems.size () - 2);
915 /* TODO: some sort of damping iso -> plain horizontal */
916 if (concaveness1 || concaveness2 > r2)
918 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
919 Real r = pos.linear_combination (0);
920 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
921 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
924 return SCM_UNSPECIFIED;
927 /* This neat trick is by Werner Lemberg,
928 damped = tanh (slope)
929 corresponds with some tables in [Wanske] CHECKME */
930 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
932 Beam::slope_damping (SCM smob)
934 Grob *me = unsmob_grob (smob);
936 if (visible_stem_count (me) <= 1)
937 return SCM_UNSPECIFIED;
939 SCM s = me->get_grob_property ("damping");
940 int damping = gh_scm2int (s);
944 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
945 Real dy = pos.delta ();
947 // ugh -> use commonx
948 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
949 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
950 Real dydx = dy && dx ? dy/dx : 0;
951 dydx = 0.6 * tanh (dydx) / damping;
953 Real damped_dy = dydx * dx;
954 pos[LEFT] += (dy - damped_dy) / 2;
955 pos[RIGHT] -= (dy - damped_dy) / 2;
957 me->set_grob_property ("positions", ly_interval2scm (pos));
959 return SCM_UNSPECIFIED;
963 Calculate the Y position of the stem-end, given the Y-left, Y-right
964 in POS, and for stem S.
967 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
969 int beam_multiplicity = get_multiplicity (me);
970 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
972 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
973 Real interbeam = get_interbeam (me);
975 // ugh -> use commonx
976 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
977 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
978 Real dy = pos.delta ();
979 Real stem_y = (dy && dx
980 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
985 Direction first_dir = Directional_element_interface::get (first_visible_stem (me));
986 Direction my_dir = Directional_element_interface::get (s);
988 if (my_dir != first_dir)
991 WTF is happening here ?
993 It looks as if this is some kind of fixup for multiple kneed
994 beams to get a piece of stem at the #.
1005 Rules for this kind of stuff are hairy. In any event, the
1006 current stem should look at the multiplicity of its
1012 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1014 // huh, why not for first visible?
1017 What the heck is happening here??
1019 Grob *last_visible = last_visible_stem (me);
1022 if ( Staff_symbol_referencer::staff_symbol_l (s)
1023 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1024 stem_y += Directional_element_interface::get (me)
1025 * (beam_multiplicity - stem_multiplicity) * interbeam;
1028 programming_error ("No last visible stem");
1035 Hmm. At this time, beam position and slope are determined. Maybe,
1036 stem directions and length should set to relative to the chord's
1037 position of the beam. */
1039 Beam::set_stem_lengths (Grob *me)
1041 Link_array<Item> stems=
1042 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1044 if (stems.size () <= 1)
1047 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1048 for (int i=1; i < stems.size (); i++)
1049 if (!Stem::invisible_b (stems[i]))
1050 common = common->common_refpoint (stems[i], Y_AXIS);
1052 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1053 Real staff_space = Staff_symbol_referencer::staff_space (me);
1059 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1060 Direction dir = Directional_element_interface::get (me);
1061 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1064 for (int i=0; i < stems.size (); i++)
1067 if (Stem::invisible_b (s))
1070 Real stem_y = calc_stem_y (me, s, pos);
1073 // doesn't play well with dvips
1075 if (Stem::get_direction (s) == dir)
1076 stem_y += Stem::get_direction (s) * thick / 2;
1079 /* caution: stem measures in staff-positions */
1080 Real id = me->relative_coordinate (common, Y_AXIS)
1081 - stems[i]->relative_coordinate (common, Y_AXIS);
1082 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1087 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1089 Link_array<Grob> stems=
1090 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1093 for (int i=0; i < stems.size (); i++)
1097 /* Don't overwrite user override (?) */
1098 if (Stem::beam_count (stems[i], d) == -1
1099 /* Don't set beaming for outside of outer stems */
1100 && ! (d == LEFT && i == 0)
1101 && ! (d == RIGHT && i == stems.size () -1))
1103 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1104 Stem::set_beaming (stems[i], b, d);
1107 while (flip (&d) != LEFT);
1114 beams to go with one stem.
1118 The beam should be constructed by one function that knows where the
1119 X and Y points are, and only inspects the stems to obtain
1120 multiplicity and stem directions.
1124 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1126 // ugh -> use commonx
1128 && ! (next->relative_coordinate (0, X_AXIS)
1129 > here->relative_coordinate (0, X_AXIS)))
1131 && ! (prev->relative_coordinate (0, X_AXIS)
1132 < here->relative_coordinate (0, X_AXIS))))
1133 programming_error ("Beams are not left-to-right");
1135 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1136 Real bdy = get_interbeam (me);
1139 Molecule rightbeams;
1142 if (!Stem::first_head (here))
1146 int t = Stem::duration_log (here);
1148 SCM proc = me->get_grob_property ("flag-width-function");
1149 SCM result = gh_call1 (proc, gh_int2scm (t));
1150 nw_f = gh_scm2double (result);
1154 /* [Tremolo] beams on whole notes may not have direction set? */
1155 Direction dir = Directional_element_interface::get (here);
1157 /* half beams extending to the left. */
1160 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1161 - Stem::beam_count (prev, RIGHT);
1162 int lwholebeams= Stem::beam_count (here, LEFT)
1163 <? Stem::beam_count (prev, RIGHT);
1165 /* Half beam should be one note-width,
1166 but let's make sure two half-beams never touch */
1168 // FIXME: TODO (check) stem width / sloped beams
1169 Real w = here->relative_coordinate (0, X_AXIS)
1170 - prev->relative_coordinate (0, X_AXIS);
1171 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1173 * me->paper_l ()->get_var ("linethickness");
1177 if (lhalfs) // generates warnings if not
1178 a = Lookup::beam (dydx, w + stem_w, thick);
1179 a.translate (Offset (-w, -w * dydx));
1180 a.translate_axis (-stem_w/2, X_AXIS);
1181 for (int j = 0; j < lhalfs; j++)
1184 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1185 leftbeams.add_molecule (b);
1191 int rhalfs = Stem::beam_count (here, RIGHT)
1192 - Stem::beam_count (next, LEFT);
1193 int rwholebeams= Stem::beam_count (here, RIGHT)
1194 <? Stem::beam_count (next, LEFT);
1196 Real w = next->relative_coordinate (0, X_AXIS)
1197 - here->relative_coordinate (0, X_AXIS);
1199 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1201 * me->paper_l ()->get_var ("linethickness");
1203 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1204 a.translate_axis (- stem_w/2, X_AXIS);
1208 SCM gap = me->get_grob_property ("gap");
1209 if (gh_number_p (gap))
1211 int gap_i = gh_scm2int ((gap));
1212 int nogap = rwholebeams - gap_i;
1214 for (; j < nogap; j++)
1217 b.translate_axis (-dir * bdy * j, Y_AXIS);
1218 rightbeams.add_molecule (b);
1220 if (Stem::invisible_b (here))
1225 a = Lookup::beam (dydx, w + stem_w, thick);
1228 for (; j < rwholebeams; j++)
1232 if (Stem::invisible_b (here))
1233 // ugh, see chord-tremolo.ly
1234 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1237 b.translate (Offset (tx, -dir * bdy * j));
1238 rightbeams.add_molecule (b);
1243 a = Lookup::beam (dydx, w, thick);
1245 for (; j < rwholebeams + rhalfs; j++)
1248 b.translate_axis (- dir * bdy * j, Y_AXIS);
1249 rightbeams.add_molecule (b);
1253 leftbeams.add_molecule (rightbeams);
1259 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1261 Beam::brew_molecule (SCM smob)
1263 Grob *me =unsmob_grob (smob);
1266 if (!gh_pair_p (me->get_grob_property ("stems")))
1269 Link_array<Item>stems =
1270 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1271 if (visible_stem_count (me))
1273 // ugh -> use commonx
1274 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1275 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1279 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1280 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1283 SCM posns = me->get_grob_property ("positions");
1285 if (!ly_number_pair_p (posns))
1287 programming_error ("No beam posns");
1288 pos = Interval (0,0);
1291 pos= ly_scm2interval (posns);
1292 Real dy = pos.delta ();
1293 Real dydx = dy && dx ? dy/dx : 0;
1296 Direction firstdir = Directional_element_interface::get ( Beam::first_visible_stem (me) );
1298 for (int i=0; i < stems.size (); i++)
1300 Item *item = stems[i];
1301 Item *prev = (i > 0)? stems[i-1] : 0;
1302 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1306 Molecule sb = stem_beams (me, item, next, prev, dydx);
1307 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1308 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1310 Direction sd = Stem::get_direction (item);
1311 mol.add_molecule (sb);
1314 mol.translate_axis (x0
1315 - dynamic_cast<Spanner*> (me)
1316 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1319 #if (DEBUG_QUANTING)
1322 This code prints the demerits for each beam. Perhaps this
1323 should be switchable for those who want to twiddle with the
1329 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1332 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1335 SCM properties = Font_interface::font_alist_chain (me);
1338 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1339 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1343 return mol.smobbed_copy ();
1347 Beam::forced_stem_count (Grob *me)
1349 Link_array<Item>stems =
1350 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1352 for (int i=0; i < stems.size (); i++)
1356 if (Stem::invisible_b (s))
1359 if (((int)Stem::chord_start_y (s))
1360 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1370 Beam::visible_stem_count (Grob *me)
1372 Link_array<Item>stems =
1373 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1375 for (int i = stems.size (); i--;)
1377 if (!Stem::invisible_b (stems[i]))
1384 Beam::first_visible_stem (Grob *me)
1386 Link_array<Item>stems =
1387 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1389 for (int i = 0; i < stems.size (); i++)
1391 if (!Stem::invisible_b (stems[i]))
1398 Beam::last_visible_stem (Grob *me)
1400 Link_array<Item>stems =
1401 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1402 for (int i = stems.size (); i--;)
1404 if (!Stem::invisible_b (stems[i]))
1414 handle rest under beam (do_post: beams are calculated now)
1415 what about combination of collisions and rest under beam.
1419 rest -> stem -> beam -> interpolate_y_position ()
1421 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1423 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1425 Grob *rest = unsmob_grob (element_smob);
1426 Axis a = (Axis) gh_scm2int (axis);
1428 assert (a == Y_AXIS);
1430 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1433 return gh_double2scm (0.0);
1434 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1436 || !Beam::has_interface (beam)
1437 || !Beam::visible_stem_count (beam))
1438 return gh_double2scm (0.0);
1440 // make callback for rest from this.
1441 // todo: make sure this calced already.
1443 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1444 Interval pos (0, 0);
1445 SCM s = beam->get_grob_property ("positions");
1446 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1447 pos = ly_scm2interval (s);
1449 Real dy = pos.delta ();
1450 // ugh -> use commonx
1451 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1452 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1453 Real dydx = dy && dx ? dy/dx : 0;
1455 Direction d = Stem::get_direction (stem);
1456 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1458 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1461 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1464 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1466 minimum_dist + -d * (beamy - rest_dim) >? 0;
1468 int stafflines = Staff_symbol_referencer::line_count (rest);
1470 // move discretely by half spaces.
1471 int discrete_dist = int (ceil (dist));
1473 // move by whole spaces inside the staff.
1474 if (discrete_dist < stafflines+1)
1475 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1477 return gh_double2scm (-d * discrete_dist);
1483 ADD_INTERFACE (Beam, "beam-interface",
1486 #'thickness= weight of beams, in staffspace
1489 We take the least squares line through the ideal-length stems, and
1490 then damp that using
1492 damped = tanh (slope)
1494 this gives an unquantized left and right position for the beam end.
1495 Then we take all combinations of quantings near these left and right
1496 positions, and give them a score (according to how close they are to
1497 the ideal slope, how close the result is to the ideal stems, etc.). We
1498 take the best scoring combination.
1501 "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");