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_;
592 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
594 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
595 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
598 demerit_score *= 2.0 / stems.size ();
600 return demerit_score;
604 Beam::score_slopes_dy (Grob *me,
606 Real dy_mus, Real dy_damp)
611 if (sign (dy_damp) != sign (dy))
613 dem += DAMPING_DIRECTIION_PENALTY;
616 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
617 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
625 return x - floor (x);
629 Beam::score_forbidden_quants (Grob*me,
633 Real thickness, Real interbeam,
635 Direction ldir, Direction rdir)
640 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
641 dem += INTER_QUANT_PENALTY;
642 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
643 dem += INTER_QUANT_PENALTY;
645 // todo: use multiplicity of outer stems.
646 if (multiplicity >= 2)
650 Real sit = (thickness - slt) / 2;
652 Real hang = 1.0 - (thickness - slt) / 2;
655 if (fabs (yl - ldir * interbeam) < rad
656 && fabs (my_modf (yl) - inter) < 1e-3)
657 dem += SECONDARY_BEAM_DEMERIT;
658 if (fabs (yr - rdir * interbeam) < rad
659 && fabs (my_modf (yr) - inter) < 1e-3)
660 dem += SECONDARY_BEAM_DEMERIT;
665 Can't we simply compute the distance between the nearest
666 staffline and the secondary beam? That would get rid of the
667 silly case analysis here (which is probably not when we have
668 different beam-thicknesses.)
674 // hmm, without Interval/Drul_array, you get ~ 4x same code...
675 if (fabs (yl - ldir * interbeam) < rad + inter)
677 if (ldir == UP && dy <= eps
678 && fabs (my_modf (yl) - sit) < eps)
679 dem += SECONDARY_BEAM_DEMERIT;
681 if (ldir == DOWN && dy >= eps
682 && fabs (my_modf (yl) - hang) < eps)
683 dem += SECONDARY_BEAM_DEMERIT;
686 if (fabs (yr - rdir * interbeam) < rad + inter)
688 if (rdir == UP && dy >= eps
689 && fabs (my_modf (yr) - sit) < eps)
690 dem += SECONDARY_BEAM_DEMERIT;
692 if (rdir == DOWN && dy <= eps
693 && fabs (my_modf (yr) - hang) < eps)
694 dem += SECONDARY_BEAM_DEMERIT;
697 if (multiplicity >= 3)
699 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
701 if (ldir == UP && dy <= eps
702 && fabs (my_modf (yl) - straddle) < eps)
703 dem += SECONDARY_BEAM_DEMERIT;
705 if (ldir == DOWN && dy >= eps
706 && fabs (my_modf (yl) - straddle) < eps)
707 dem += SECONDARY_BEAM_DEMERIT;
710 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
712 if (rdir == UP && dy >= eps
713 && fabs (my_modf (yr) - straddle) < eps)
714 dem += SECONDARY_BEAM_DEMERIT;
716 if (rdir == DOWN && dy <= eps
717 && fabs (my_modf (yr) - straddle) < eps)
718 dem += SECONDARY_BEAM_DEMERIT;
728 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
730 Beam::least_squares (SCM smob)
732 Grob *me = unsmob_grob (smob);
734 int count = visible_stem_count (me);
739 me->set_grob_property ("positions", ly_interval2scm (pos));
740 return SCM_UNSPECIFIED;
743 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
744 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
748 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
749 Stem::chord_start_y (last_visible_stem (me)));
753 TODO : use scoring for this.
755 complicated, because we take stem-info.ideal for determining
759 /* Make simple beam on middle line have small tilt */
760 if (!ideal[LEFT] && chord.delta () && count == 2)
766 Direction d = (Direction) (sign (chord.delta ()) * UP);
767 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
778 Array<Offset> ideals;
780 // ugh -> use commonx
781 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
782 Link_array<Item> stems=
783 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
785 for (int i=0; i < stems.size (); i++)
788 if (Stem::invisible_b (s))
790 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
791 Stem::calc_stem_info (s).ideal_y_));
795 minimise_least_squares (&dydx, &y, ideals);
797 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
799 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
801 pos = Interval (y, (y+dy));
804 me->set_grob_property ("positions", ly_interval2scm (pos));
805 return SCM_UNSPECIFIED;
808 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
810 Beam::check_concave (SCM smob)
812 Grob *me = unsmob_grob (smob);
814 Link_array<Item> stems =
815 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
817 for (int i = 0; i < stems.size ();)
819 if (Stem::invisible_b (stems[i]))
825 if (stems.size () < 3)
826 return SCM_UNSPECIFIED;
829 /* Concaveness #1: If distance of an inner notehead to line between
830 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
831 beam is concave (Heinz Stolba).
833 In the case of knees, the line connecting outer heads is often
834 not related to the beam slope (it may even go in the other
835 direction). Skip the check when the outer stems point in
836 different directions. --hwn
839 bool concaveness1 = false;
840 SCM gap = me->get_grob_property ("concaveness-gap");
841 if (gh_number_p (gap)
842 && Stem::get_direction(stems.top ())
843 == Stem::get_direction(stems[0]))
845 Real r1 = gh_scm2double (gap);
846 Real dy = Stem::chord_start_y (stems.top ())
847 - Stem::chord_start_y (stems[0]);
850 Real slope = dy / (stems.size () - 1);
852 Real y0 = Stem::chord_start_y (stems[0]);
853 for (int i = 1; i < stems.size () - 1; i++)
855 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
865 /* Concaveness #2: Sum distances of inner noteheads that fall
866 outside the interval of the two outer noteheads.
868 We only do this for beams where first and last stem have the same
872 Note that "convex" stems compensate for "concave" stems.
873 (is that intentional?) --hwn.
876 Real concaveness2 = 0;
877 SCM thresh = me->get_grob_property ("concaveness-threshold");
878 Real r2 = infinity_f;
879 if (!concaveness1 && gh_number_p (thresh)
880 && Stem::get_direction(stems.top ())
881 == Stem::get_direction(stems[0]))
883 r2 = gh_scm2double (thresh);
885 Direction dir = Stem::get_direction(stems.top ());
887 Interval iv (Stem::chord_start_y (stems[0]),
888 Stem::chord_start_y (stems.top ()));
890 if (iv[MAX] < iv[MIN])
893 for (int i = 1; i < stems.size () - 1; i++)
895 Real f = Stem::chord_start_y (stems[i]);
896 concave += ((f - iv[MAX] ) >? 0) +
897 ((f - iv[MIN] ) <? 0);
900 concaveness2 = concave / (stems.size () - 2);
902 /* ugh: this is the a kludge to get
903 input/regression/beam-concave.ly to behave as
907 huh? we're dividing twice (which is not scalable) meaning that
908 the longer the beam, the more unlikely it will be
909 concave. Maybe you would even expect the other way around??
914 concaveness2 /= (stems.size () - 2);
917 /* TODO: some sort of damping iso -> plain horizontal */
918 if (concaveness1 || concaveness2 > r2)
920 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
921 Real r = pos.linear_combination (0);
922 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
923 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
926 return SCM_UNSPECIFIED;
929 /* This neat trick is by Werner Lemberg,
930 damped = tanh (slope)
931 corresponds with some tables in [Wanske] CHECKME */
932 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
934 Beam::slope_damping (SCM smob)
936 Grob *me = unsmob_grob (smob);
938 if (visible_stem_count (me) <= 1)
939 return SCM_UNSPECIFIED;
941 SCM s = me->get_grob_property ("damping");
942 int damping = gh_scm2int (s);
946 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
947 Real dy = pos.delta ();
949 // ugh -> use commonx
950 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
951 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
952 Real dydx = dy && dx ? dy/dx : 0;
953 dydx = 0.6 * tanh (dydx) / damping;
955 Real damped_dy = dydx * dx;
956 pos[LEFT] += (dy - damped_dy) / 2;
957 pos[RIGHT] -= (dy - damped_dy) / 2;
959 me->set_grob_property ("positions", ly_interval2scm (pos));
961 return SCM_UNSPECIFIED;
965 Calculate the Y position of the stem-end, given the Y-left, Y-right
966 in POS, and for stem S.
969 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
971 int beam_multiplicity = get_multiplicity (me);
972 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
974 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
975 Real interbeam = get_interbeam (me);
977 // ugh -> use commonx
978 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
979 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
980 Real dy = pos.delta ();
981 Real stem_y = (dy && dx
982 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
987 Direction first_dir = Directional_element_interface::get (first_visible_stem (me));
988 Direction my_dir = Directional_element_interface::get (s);
990 if (my_dir != first_dir)
993 WTF is happening here ?
995 It looks as if this is some kind of fixup for multiple kneed
996 beams to get a piece of stem at the #.
1007 Rules for this kind of stuff are hairy. In any event, the
1008 current stem should look at the multiplicity of its
1014 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1016 // huh, why not for first visible?
1019 What the heck is happening here??
1021 Grob *last_visible = last_visible_stem (me);
1024 if ( Staff_symbol_referencer::staff_symbol_l (s)
1025 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1026 stem_y += Directional_element_interface::get (me)
1027 * (beam_multiplicity - stem_multiplicity) * interbeam;
1030 programming_error ("No last visible stem");
1037 Hmm. At this time, beam position and slope are determined. Maybe,
1038 stem directions and length should set to relative to the chord's
1039 position of the beam. */
1041 Beam::set_stem_lengths (Grob *me)
1043 Link_array<Item> stems=
1044 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1046 if (stems.size () <= 1)
1049 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1050 for (int i=1; i < stems.size (); i++)
1051 if (!Stem::invisible_b (stems[i]))
1052 common = common->common_refpoint (stems[i], Y_AXIS);
1054 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1055 Real staff_space = Staff_symbol_referencer::staff_space (me);
1061 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1062 Direction dir = Directional_element_interface::get (me);
1063 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1066 for (int i=0; i < stems.size (); i++)
1069 if (Stem::invisible_b (s))
1072 Real stem_y = calc_stem_y (me, s, pos);
1075 // doesn't play well with dvips
1077 if (Stem::get_direction (s) == dir)
1078 stem_y += Stem::get_direction (s) * thick / 2;
1081 /* caution: stem measures in staff-positions */
1082 Real id = me->relative_coordinate (common, Y_AXIS)
1083 - stems[i]->relative_coordinate (common, Y_AXIS);
1084 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1089 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1091 Link_array<Grob> stems=
1092 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1095 for (int i=0; i < stems.size (); i++)
1099 /* Don't overwrite user override (?) */
1100 if (Stem::beam_count (stems[i], d) == -1
1101 /* Don't set beaming for outside of outer stems */
1102 && ! (d == LEFT && i == 0)
1103 && ! (d == RIGHT && i == stems.size () -1))
1105 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1106 Stem::set_beaming (stems[i], b, d);
1109 while (flip (&d) != LEFT);
1116 beams to go with one stem.
1120 The beam should be constructed by one function that knows where the
1121 X and Y points are, and only inspects the stems to obtain
1122 multiplicity and stem directions.
1126 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1128 // ugh -> use commonx
1130 && ! (next->relative_coordinate (0, X_AXIS)
1131 > here->relative_coordinate (0, X_AXIS)))
1133 && ! (prev->relative_coordinate (0, X_AXIS)
1134 < here->relative_coordinate (0, X_AXIS))))
1135 programming_error ("Beams are not left-to-right");
1137 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1138 Real bdy = get_interbeam (me);
1141 Molecule rightbeams;
1144 if (!Stem::first_head (here))
1148 int t = Stem::duration_log (here);
1150 SCM proc = me->get_grob_property ("flag-width-function");
1151 SCM result = gh_call1 (proc, gh_int2scm (t));
1152 nw_f = gh_scm2double (result);
1156 /* [Tremolo] beams on whole notes may not have direction set? */
1157 Direction dir = Directional_element_interface::get (here);
1159 /* half beams extending to the left. */
1162 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1163 - Stem::beam_count (prev, RIGHT);
1164 int lwholebeams= Stem::beam_count (here, LEFT)
1165 <? Stem::beam_count (prev, RIGHT);
1167 /* Half beam should be one note-width,
1168 but let's make sure two half-beams never touch */
1170 // FIXME: TODO (check) stem width / sloped beams
1171 Real w = here->relative_coordinate (0, X_AXIS)
1172 - prev->relative_coordinate (0, X_AXIS);
1173 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1175 * me->paper_l ()->get_var ("linethickness");
1179 if (lhalfs) // generates warnings if not
1180 a = Lookup::beam (dydx, w + stem_w, thick);
1181 a.translate (Offset (-w, -w * dydx));
1182 a.translate_axis (-stem_w/2, X_AXIS);
1183 for (int j = 0; j < lhalfs; j++)
1186 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1187 leftbeams.add_molecule (b);
1193 int rhalfs = Stem::beam_count (here, RIGHT)
1194 - Stem::beam_count (next, LEFT);
1195 int rwholebeams= Stem::beam_count (here, RIGHT)
1196 <? Stem::beam_count (next, LEFT);
1198 Real w = next->relative_coordinate (0, X_AXIS)
1199 - here->relative_coordinate (0, X_AXIS);
1201 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1203 * me->paper_l ()->get_var ("linethickness");
1205 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1206 a.translate_axis (- stem_w/2, X_AXIS);
1210 SCM gap = me->get_grob_property ("gap");
1211 if (gh_number_p (gap))
1213 int gap_i = gh_scm2int ((gap));
1214 int nogap = rwholebeams - gap_i;
1216 for (; j < nogap; j++)
1219 b.translate_axis (-dir * bdy * j, Y_AXIS);
1220 rightbeams.add_molecule (b);
1222 if (Stem::invisible_b (here))
1227 a = Lookup::beam (dydx, w + stem_w, thick);
1230 for (; j < rwholebeams; j++)
1234 if (Stem::invisible_b (here))
1235 // ugh, see chord-tremolo.ly
1236 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1239 b.translate (Offset (tx, -dir * bdy * j));
1240 rightbeams.add_molecule (b);
1245 a = Lookup::beam (dydx, w, thick);
1247 for (; j < rwholebeams + rhalfs; j++)
1250 b.translate_axis (- dir * bdy * j, Y_AXIS);
1251 rightbeams.add_molecule (b);
1255 leftbeams.add_molecule (rightbeams);
1261 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1263 Beam::brew_molecule (SCM smob)
1265 Grob *me =unsmob_grob (smob);
1268 if (!gh_pair_p (me->get_grob_property ("stems")))
1271 Link_array<Item>stems =
1272 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1273 if (visible_stem_count (me))
1275 // ugh -> use commonx
1276 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1277 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1281 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1282 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1285 SCM posns = me->get_grob_property ("positions");
1287 if (!ly_number_pair_p (posns))
1289 programming_error ("No beam posns");
1290 pos = Interval (0,0);
1293 pos= ly_scm2interval (posns);
1294 Real dy = pos.delta ();
1295 Real dydx = dy && dx ? dy/dx : 0;
1298 Direction firstdir = Directional_element_interface::get ( Beam::first_visible_stem (me) );
1300 for (int i=0; i < stems.size (); i++)
1302 Item *item = stems[i];
1303 Item *prev = (i > 0)? stems[i-1] : 0;
1304 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1308 Molecule sb = stem_beams (me, item, next, prev, dydx);
1309 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1310 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1312 Direction sd = Stem::get_direction (item);
1313 mol.add_molecule (sb);
1316 mol.translate_axis (x0
1317 - dynamic_cast<Spanner*> (me)
1318 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1321 #if (DEBUG_QUANTING)
1324 This code prints the demerits for each beam. Perhaps this
1325 should be switchable for those who want to twiddle with the
1331 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1334 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1337 SCM properties = Font_interface::font_alist_chain (me);
1340 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1341 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1345 return mol.smobbed_copy ();
1349 Beam::forced_stem_count (Grob *me)
1351 Link_array<Item>stems =
1352 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1354 for (int i=0; i < stems.size (); i++)
1358 if (Stem::invisible_b (s))
1361 if (((int)Stem::chord_start_y (s))
1362 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1372 Beam::visible_stem_count (Grob *me)
1374 Link_array<Item>stems =
1375 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1377 for (int i = stems.size (); i--;)
1379 if (!Stem::invisible_b (stems[i]))
1386 Beam::first_visible_stem (Grob *me)
1388 Link_array<Item>stems =
1389 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1391 for (int i = 0; i < stems.size (); i++)
1393 if (!Stem::invisible_b (stems[i]))
1400 Beam::last_visible_stem (Grob *me)
1402 Link_array<Item>stems =
1403 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1404 for (int i = stems.size (); i--;)
1406 if (!Stem::invisible_b (stems[i]))
1416 handle rest under beam (do_post: beams are calculated now)
1417 what about combination of collisions and rest under beam.
1421 rest -> stem -> beam -> interpolate_y_position ()
1423 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1425 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1427 Grob *rest = unsmob_grob (element_smob);
1428 Axis a = (Axis) gh_scm2int (axis);
1430 assert (a == Y_AXIS);
1432 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1435 return gh_double2scm (0.0);
1436 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1438 || !Beam::has_interface (beam)
1439 || !Beam::visible_stem_count (beam))
1440 return gh_double2scm (0.0);
1442 // make callback for rest from this.
1443 // todo: make sure this calced already.
1445 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1446 Interval pos (0, 0);
1447 SCM s = beam->get_grob_property ("positions");
1448 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1449 pos = ly_scm2interval (s);
1451 Real dy = pos.delta ();
1452 // ugh -> use commonx
1453 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1454 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1455 Real dydx = dy && dx ? dy/dx : 0;
1457 Direction d = Stem::get_direction (stem);
1458 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1460 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1463 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1466 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1468 minimum_dist + -d * (beamy - rest_dim) >? 0;
1470 int stafflines = Staff_symbol_referencer::line_count (rest);
1472 // move discretely by half spaces.
1473 int discrete_dist = int (ceil (dist));
1475 // move by whole spaces inside the staff.
1476 if (discrete_dist < stafflines+1)
1477 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1479 return gh_double2scm (-d * discrete_dist);
1485 ADD_INTERFACE (Beam, "beam-interface",
1488 #'thickness= weight of beams, in staffspace
1491 We take the least squares line through the ideal-length stems, and
1492 then damp that using
1494 damped = tanh (slope)
1496 this gives an unquantized left and right position for the beam end.
1497 Then we take all combinations of quantings near these left and right
1498 positions, and give them a score (according to how close they are to
1499 the ideal slope, how close the result is to the ideal stems, etc.). We
1500 take the best scoring combination.
1503 "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");