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);
94 Beam::get_multiplicity (Grob *me)
97 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
99 Grob *sc = unsmob_grob (ly_car (s));
101 if (Stem::has_interface (sc))
102 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
107 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
109 Beam::space_function (SCM smob, SCM multiplicity)
111 Grob *me = unsmob_grob (smob);
113 Real staff_space = Staff_symbol_referencer::staff_space (me);
114 Real line = me->paper_l ()->get_var ("linethickness");
115 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
118 Real interbeam = gh_scm2int (multiplicity) < 4
119 ? (2*staff_space + line - thickness) / 2.0
120 : (3*staff_space + line - thickness) / 3.0;
122 return gh_double2scm (interbeam);
126 /* After pre-processing all directions should be set.
127 Several post-processing routines (stem, slur, script) need stem/beam
129 Currenly, this means that beam has set all stem's directions.
130 [Alternatively, stems could set its own directions, according to
131 their beam, during 'final-pre-processing'.] */
132 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
134 Beam::before_line_breaking (SCM smob)
136 Grob *me = unsmob_grob (smob);
138 /* Beams with less than 2 two stems don't make much sense, but could happen
143 For a beam that only has one stem, we try to do some disappearance magic:
144 we revert the flag, and move on to The Eternal Engraving Fields. */
146 int count = visible_stem_count (me);
149 me->warning (_ ("beam has less than two visible stems"));
151 SCM stems = me->get_grob_property ("stems");
152 if (scm_ilength (stems) == 1)
154 me->warning (_ ("Beam has less than two stems. Removing beam."));
156 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
159 return SCM_UNSPECIFIED;
161 else if (scm_ilength (stems) == 0)
164 return SCM_UNSPECIFIED;
169 if (!Directional_element_interface::get (me))
170 Directional_element_interface::set (me, get_default_dir (me));
172 consider_auto_knees (me);
173 set_stem_directions (me);
174 set_stem_shorten (me);
180 Beam::get_default_dir (Grob *me)
182 Drul_array<int> total;
183 total[UP] = total[DOWN] = 0;
184 Drul_array<int> count;
185 count[UP] = count[DOWN] = 0;
188 Link_array<Item> stems=
189 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
191 for (int i=0; i <stems.size (); i++)
194 Direction sd = Directional_element_interface::get (s);
196 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
197 int current = sd ? (1 + d * sd)/2 : center_distance;
204 } while (flip (&d) != DOWN);
206 SCM func = me->get_grob_property ("dir-function");
207 SCM s = gh_call2 (func,
208 gh_cons (gh_int2scm (count[UP]),
209 gh_int2scm (count[DOWN])),
210 gh_cons (gh_int2scm (total[UP]),
211 gh_int2scm (total[DOWN])));
213 if (gh_number_p (s) && gh_scm2int (s))
216 /* If dir is not determined: get default */
217 return to_dir (me->get_grob_property ("neutral-direction"));
221 /* Set all stems with non-forced direction to beam direction.
222 Urg: non-forced should become `without/with unforced' direction,
223 once stem gets cleaned-up. */
225 Beam::set_stem_directions (Grob *me)
227 Link_array<Item> stems
228 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
229 Direction d = Directional_element_interface::get (me);
231 for (int i=0; i <stems.size (); i++)
234 SCM force = s->remove_grob_property ("dir-forced");
235 if (!gh_boolean_p (force) || !gh_scm2bool (force))
236 Directional_element_interface::set (s, d);
240 /* Simplistic auto-knees; only consider vertical gap between two
243 `Forced' stem directions are ignored. If you don't want auto-knees,
244 don't set, or unset auto-knee-gap. */
246 Beam::consider_auto_knees (Grob *me)
248 SCM scm = me->get_grob_property ("auto-knee-gap");
250 if (gh_number_p (scm))
254 Real staff_space = Staff_symbol_referencer::staff_space (me);
255 Real gap = gh_scm2double (scm) / staff_space;
257 Direction d = Directional_element_interface::get (me);
258 Link_array<Item> stems=
259 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
261 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
262 for (int i=1; i < stems.size (); i++)
263 if (!Stem::invisible_b (stems[i]))
264 common = common->common_refpoint (stems[i], Y_AXIS);
267 for (int i=1; i < stems.size (); i++)
269 if (!Stem::invisible_b (stems[i-1]))
271 if (Stem::invisible_b (stems[l]))
273 if (Stem::invisible_b (stems[i]))
276 Real left = Stem::extremal_heads (stems[l])[d]
277 ->relative_coordinate (common, Y_AXIS);
278 Real right = Stem::extremal_heads (stems[i])[-d]
279 ->relative_coordinate (common, Y_AXIS);
281 Real dy = right - left;
285 knee_y = (right + left) / 2;
293 for (int i=0; i < stems.size (); i++)
295 if (Stem::invisible_b (stems[i]))
298 Real y = Stem::extremal_heads (stems[i])[d]
299 ->relative_coordinate (common, Y_AXIS);
301 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
302 s->set_grob_property ("dir-forced", SCM_BOOL_T);
308 /* Set stem's shorten property if unset.
311 take some y-position (chord/beam/nearest?) into account
312 scmify forced-fraction
316 why is shorten stored in beam, and not directly in stem?
320 Beam::set_stem_shorten (Grob *m)
322 Spanner*me = dynamic_cast<Spanner*> (m);
324 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
326 int multiplicity = get_multiplicity (me);
328 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
329 if (shorten == SCM_EOL)
332 int sz = scm_ilength (shorten);
334 Real staff_space = Staff_symbol_referencer::staff_space (me);
335 SCM shorten_elt = scm_list_ref (shorten,
336 gh_int2scm (multiplicity <? (sz - 1)));
337 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
339 /* your similar cute comment here */
340 shorten_f *= forced_fraction;
342 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
345 /* Call list of y-dy-callbacks, that handle setting of
346 grob-properties y, dy.
348 User may set grob-properties: y-position-hs and height-hs
349 (to be fixed) that override the calculated y and dy.
351 Because y and dy cannot be calculated and quanted separately, we
352 always calculate both, then check for user override. */
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.
431 const int REGION_SIZE = 2;
432 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
433 for (int j = 0; j < num_quants; j++)
435 quantsl.push (i + quants[j] + int (yl));
436 quantsr.push (i + quants[j] + int (yr));
439 Array<Quant_score> qscores;
441 for (int l =0; l < quantsl.size (); l++)
442 for (int r =0; r < quantsr.size (); r++)
454 This is a longish function, but we don't separate this out into
455 neat modular separate subfunctions, as the subfunctions would be
456 called for many values of YL, YR. By precomputing various
457 parameters outside of the loop, we can save a lot of time.
460 for (int i = qscores.size (); i--;)
461 if (qscores[i].demerits < 100)
464 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
468 Real rad = Staff_symbol_referencer::staff_radius (me);
469 int multiplicity = get_multiplicity (me);
470 Real interbeam = multiplicity < 4
471 ? (2*ss + slt - thickness) / 2.0
472 : (3*ss + slt - thickness) / 3.0;
474 for (int i = qscores.size (); i--;)
475 if (qscores[i].demerits < 100)
478 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
479 rad, slt, thickness, interbeam,
485 Do stem lengths. These depend on YL and YR linearly, so we can
486 precompute for every stem 2 factors.
488 Link_array<Grob> stems=
489 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
490 Array<Stem_info> stem_infos;
491 Array<Real> lbase_lengths;
492 Array<Real> rbase_lengths;
494 Array<int> directions;
496 Drul_array<bool> dirs_found(0,0);
498 for (int i= 0; i < stems.size(); i++)
501 stem_infos.push( Stem::calc_stem_info (s));
503 Real b = calc_stem_y (me, s, Interval (1,0));
504 lbase_lengths.push (b);
506 b = calc_stem_y (me, s, Interval (0,1));
507 rbase_lengths.push (b);
509 Direction d = Directional_element_interface::get( s);
511 dirs_found [d] = true;
514 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
515 for (int i = qscores.size (); i--;)
516 if (qscores[i].demerits < 100)
519 += score_stem_lengths (stems, stem_infos,
520 lbase_lengths, rbase_lengths,
522 me, qscores[i].yl, qscores[i].yr);
528 for (int i = qscores.size (); i--;)
530 if (qscores[i].demerits < best)
532 best = qscores [i].demerits ;
538 me->set_grob_property ("positions",
539 gh_cons (gh_double2scm (qscores[best_idx].yl),
540 gh_double2scm (qscores[best_idx].yr))
546 me->set_grob_property ("quant-score",
547 gh_double2scm (qscores[best_idx].demerits));
548 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
551 return SCM_UNSPECIFIED;
555 Beam::score_stem_lengths (Link_array<Grob>stems,
556 Array<Stem_info> stem_infos,
557 Array<Real> left_factor,
558 Array<Real> right_factor,
559 Array<int> directions,
561 Grob*me, Real yl, Real yr)
563 Real demerit_score = 0.0 ;
565 for (int i=0; i < stems.size (); i++)
568 if (Stem::invisible_b (s))
572 yl * left_factor[i] + right_factor[i]* yr;
574 Stem_info info = stem_infos[i];
575 Direction d = Direction (directions[i]);
577 Real pen = STEM_LENGTH_LIMIT_PENALTY;
581 demerit_score += pen * ( 0 >? (info.min_y - d * current_y));
582 demerit_score += pen * ( 0 >? (d * current_y - info.max_y));
584 demerit_score += STEM_LENGTH_DEMERIT_FACTOR * shrink_extra_weight (d * current_y - info.ideal_y);
587 demerit_score *= 2.0 / stems.size ();
589 return demerit_score;
593 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
594 Real dy_mus, Real dy_damp)
599 if (sign (dy_damp) != sign (dy))
601 dem += DAMPING_DIRECTIION_PENALTY;
604 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
607 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
614 return x - floor (x);
618 Beam::score_forbidden_quants (Grob*me,
622 Real thickness, Real interbeam,
628 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
629 dem += INTER_QUANT_PENALTY;
630 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
631 dem += INTER_QUANT_PENALTY;
633 // todo: use multiplicity of outer stems.
634 if (multiplicity >= 2)
638 Real sit = (thickness - slt) / 2;
640 Real hang = 1.0 - (thickness - slt) / 2;
642 Direction dir = Directional_element_interface::get (me);
643 if (fabs (yl - dir * interbeam) < rad
644 && fabs (my_modf (yl) - inter) < 1e-3)
645 dem += SECONDARY_BEAM_DEMERIT;
646 if (fabs (yr - dir * interbeam) < rad
647 && fabs (my_modf (yr) - inter) < 1e-3)
648 dem += SECONDARY_BEAM_DEMERIT;
653 Can't we simply compute the distance between the nearest
654 staffline and the secondary beam? That would get rid of the
655 silly case analysis here (which is probably not when we have
656 different beam-thicknesses.)
662 // hmm, without Interval/Drul_array, you get ~ 4x same code...
663 if (fabs (yl - dir * interbeam) < rad + inter)
665 if (dir == UP && dy <= eps
666 && fabs (my_modf (yl) - sit) < eps)
667 dem += SECONDARY_BEAM_DEMERIT;
669 if (dir == DOWN && dy >= eps
670 && fabs (my_modf (yl) - hang) < eps)
671 dem += SECONDARY_BEAM_DEMERIT;
674 if (fabs (yr - dir * interbeam) < rad + inter)
676 if (dir == UP && dy >= eps
677 && fabs (my_modf (yr) - sit) < eps)
678 dem += SECONDARY_BEAM_DEMERIT;
680 if (dir == DOWN && dy <= eps
681 && fabs (my_modf (yr) - hang) < eps)
682 dem += SECONDARY_BEAM_DEMERIT;
685 if (multiplicity >= 3)
687 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
689 if (dir == UP && dy <= eps
690 && fabs (my_modf (yl) - straddle) < eps)
691 dem += SECONDARY_BEAM_DEMERIT;
693 if (dir == DOWN && dy >= eps
694 && fabs (my_modf (yl) - straddle) < eps)
695 dem += SECONDARY_BEAM_DEMERIT;
698 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
700 if (dir == UP && dy >= eps
701 && fabs (my_modf (yr) - straddle) < eps)
702 dem += SECONDARY_BEAM_DEMERIT;
704 if (dir == DOWN && dy <= eps
705 && fabs (my_modf (yr) - straddle) < eps)
706 dem += SECONDARY_BEAM_DEMERIT;
716 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
718 Beam::least_squares (SCM smob)
720 Grob *me = unsmob_grob (smob);
722 int count = visible_stem_count (me);
727 me->set_grob_property ("positions", ly_interval2scm (pos));
728 return SCM_UNSPECIFIED;
731 Direction dir = Directional_element_interface::get (me);
733 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
734 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
738 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
739 Stem::chord_start_y (last_visible_stem (me)));
743 TODO : use scoring for this.
745 complicated, because we take stem-info.ideal for determining
749 /* Make simple beam on middle line have small tilt */
750 if (!ideal[LEFT] && chord.delta () && count == 2)
752 Direction d = (Direction) (sign (chord.delta ()) * dir);
753 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
766 Array<Offset> ideals;
768 // ugh -> use commonx
769 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
770 Link_array<Item> stems=
771 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
773 for (int i=0; i < stems.size (); i++)
776 if (Stem::invisible_b (s))
778 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
779 Stem::calc_stem_info (s).ideal_y));
783 minimise_least_squares (&dydx, &y, ideals);
785 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
787 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
789 pos = Interval (y*dir, (y+dy) * dir);
792 me->set_grob_property ("positions", ly_interval2scm (pos));
793 return SCM_UNSPECIFIED;
796 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
798 Beam::check_concave (SCM smob)
800 Grob *me = unsmob_grob (smob);
802 Link_array<Item> stems =
803 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
805 for (int i = 0; i < stems.size ();)
807 if (Stem::invisible_b (stems[i]))
813 if (stems.size () < 3)
814 return SCM_UNSPECIFIED;
817 /* Concaveness #1: If distance of an inner notehead to line between
818 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
819 beam is concave (Heinz Stolba).
821 In the case of knees, the line connecting outer heads is often
822 not related to the beam slope (it may even go in the other
823 direction). Skip the check when the outer stems point in
824 different directions. --hwn
827 bool concaveness1 = false;
828 SCM gap = me->get_grob_property ("concaveness-gap");
829 if (gh_number_p (gap)
830 && Stem::get_direction(stems.top ())
831 == Stem::get_direction(stems[0]))
833 Real r1 = gh_scm2double (gap);
834 Real dy = Stem::chord_start_y (stems.top ())
835 - Stem::chord_start_y (stems[0]);
838 Real slope = dy / (stems.size () - 1);
840 Real y0 = Stem::chord_start_y (stems[0]);
841 for (int i = 1; i < stems.size () - 1; i++)
843 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
853 /* Concaveness #2: Sum distances of inner noteheads that fall
854 outside the interval of the two outer noteheads */
855 Real concaveness2 = 0;
856 SCM thresh = me->get_grob_property ("concaveness-threshold");
857 Real r2 = infinity_f;
858 if (!concaveness1 && gh_number_p (thresh))
860 r2 = gh_scm2double (thresh);
862 Direction dir = Directional_element_interface::get (me);
865 Interval iv (Stem::chord_start_y (stems[0]),
866 Stem::chord_start_y (stems.top ()));
868 if (iv[MAX] < iv[MIN])
871 for (int i = 1; i < stems.size () - 1; i++)
874 Real f = Stem::chord_start_y (stems[i]);
875 if ((c = f - iv[MAX]) > 0)
877 else if ((c = f - iv[MIN]) < 0)
881 Ugh. This will mess up with knees. Direction should be
886 concaveness2 = concave / (stems.size () - 2);
887 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
888 to behave as baerenreiter. */
889 concaveness2 /= (stems.size () - 2);
892 /* TODO: some sort of damping iso -> plain horizontal */
893 if (concaveness1 || concaveness2 > r2)
895 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
896 Real r = pos.linear_combination (0);
897 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
898 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
901 return SCM_UNSPECIFIED;
904 /* This neat trick is by Werner Lemberg,
905 damped = tanh (slope)
906 corresponds with some tables in [Wanske] CHECKME */
907 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
909 Beam::slope_damping (SCM smob)
911 Grob *me = unsmob_grob (smob);
913 if (visible_stem_count (me) <= 1)
914 return SCM_UNSPECIFIED;
916 SCM s = me->get_grob_property ("damping");
917 int damping = gh_scm2int (s);
921 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
922 Real dy = pos.delta ();
924 // ugh -> use commonx
925 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
926 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
927 Real dydx = dy && dx ? dy/dx : 0;
928 dydx = 0.6 * tanh (dydx) / damping;
930 Real damped_dy = dydx * dx;
931 pos[LEFT] += (dy - damped_dy) / 2;
932 pos[RIGHT] -= (dy - damped_dy) / 2;
934 me->set_grob_property ("positions", ly_interval2scm (pos));
936 return SCM_UNSPECIFIED;
940 Calculate the Y position of the stem-end, given the Y-left, Y-right
941 in POS, and for stem S.
944 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
946 int beam_multiplicity = get_multiplicity (me);
947 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
949 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
950 Real interbeam = get_interbeam (me);
952 // ugh -> use commonx
953 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
954 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
955 Real dy = pos.delta ();
956 Real stem_y = (dy && dx
957 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
962 Direction dir = Directional_element_interface::get (me);
963 Direction sdir = Directional_element_interface::get (s);
968 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
970 // huh, why not for first visible?
972 Grob *last_visible = last_visible_stem (me);
975 if ( Staff_symbol_referencer::staff_symbol_l (s)
976 != Staff_symbol_referencer::staff_symbol_l (last_visible))
977 stem_y += Directional_element_interface::get (me)
978 * (beam_multiplicity - stem_multiplicity) * interbeam;
981 programming_error ("No last visible stem");
988 Hmm. At this time, beam position and slope are determined. Maybe,
989 stem directions and length should set to relative to the chord's
990 position of the beam. */
992 Beam::set_stem_lengths (Grob *me)
994 Link_array<Item> stems=
995 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
997 if (stems.size () <= 1)
1000 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1001 for (int i=1; i < stems.size (); i++)
1002 if (!Stem::invisible_b (stems[i]))
1003 common = common->common_refpoint (stems[i], Y_AXIS);
1005 Direction dir = Directional_element_interface::get (me);
1006 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1007 Real staff_space = Staff_symbol_referencer::staff_space (me);
1008 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1009 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1010 for (int i=0; i < stems.size (); i++)
1013 if (Stem::invisible_b (s))
1016 Real stem_y = calc_stem_y (me, s, pos);
1018 // doesn't play well with dvips
1020 if (Stem::get_direction (s) == dir)
1021 stem_y += Stem::get_direction (s) * thick / 2;
1023 /* caution: stem measures in staff-positions */
1024 Real id = me->relative_coordinate (common, Y_AXIS)
1025 - stems[i]->relative_coordinate (common, Y_AXIS);
1026 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1031 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1033 Link_array<Grob> stems=
1034 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1037 for (int i=0; i < stems.size (); i++)
1041 /* Don't overwrite user override (?) */
1042 if (Stem::beam_count (stems[i], d) == -1
1043 /* Don't set beaming for outside of outer stems */
1044 && ! (d == LEFT && i == 0)
1045 && ! (d == RIGHT && i == stems.size () -1))
1047 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1048 Stem::set_beaming (stems[i], b, d);
1051 while (flip (&d) != LEFT);
1058 beams to go with one stem.
1063 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1065 // ugh -> use commonx
1067 && ! (next->relative_coordinate (0, X_AXIS)
1068 > here->relative_coordinate (0, X_AXIS)))
1070 && ! (prev->relative_coordinate (0, X_AXIS)
1071 < here->relative_coordinate (0, X_AXIS))))
1072 programming_error ("Beams are not left-to-right");
1074 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1075 Real bdy = get_interbeam (me);
1078 Molecule rightbeams;
1081 if (!Stem::first_head (here))
1085 int t = Stem::duration_log (here);
1087 SCM proc = me->get_grob_property ("flag-width-function");
1088 SCM result = gh_call1 (proc, gh_int2scm (t));
1089 nw_f = gh_scm2double (result);
1093 Direction dir = Directional_element_interface::get (me);
1095 /* [Tremolo] beams on whole notes may not have direction set? */
1097 dir = Directional_element_interface::get (here);
1100 /* half beams extending to the left. */
1103 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1104 - Stem::beam_count (prev, RIGHT);
1105 int lwholebeams= Stem::beam_count (here, LEFT)
1106 <? Stem::beam_count (prev, RIGHT);
1108 /* Half beam should be one note-width,
1109 but let's make sure two half-beams never touch */
1111 // FIXME: TODO (check) stem width / sloped beams
1112 Real w = here->relative_coordinate (0, X_AXIS)
1113 - prev->relative_coordinate (0, X_AXIS);
1114 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1116 * me->paper_l ()->get_var ("linethickness");
1120 if (lhalfs) // generates warnings if not
1121 a = Lookup::beam (dydx, w + stem_w, thick);
1122 a.translate (Offset (-w, -w * dydx));
1123 a.translate_axis (-stem_w/2, X_AXIS);
1124 for (int j = 0; j < lhalfs; j++)
1127 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1128 leftbeams.add_molecule (b);
1134 int rhalfs = Stem::beam_count (here, RIGHT)
1135 - Stem::beam_count (next, LEFT);
1136 int rwholebeams= Stem::beam_count (here, RIGHT)
1137 <? Stem::beam_count (next, LEFT);
1139 Real w = next->relative_coordinate (0, X_AXIS)
1140 - here->relative_coordinate (0, X_AXIS);
1142 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1144 * me->paper_l ()->get_var ("linethickness");
1146 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1147 a.translate_axis (- stem_w/2, X_AXIS);
1151 SCM gap = me->get_grob_property ("gap");
1152 if (gh_number_p (gap))
1154 int gap_i = gh_scm2int ((gap));
1155 int nogap = rwholebeams - gap_i;
1157 for (; j < nogap; j++)
1160 b.translate_axis (-dir * bdy * j, Y_AXIS);
1161 rightbeams.add_molecule (b);
1163 if (Stem::invisible_b (here))
1168 a = Lookup::beam (dydx, w + stem_w, thick);
1171 for (; j < rwholebeams; j++)
1175 if (Stem::invisible_b (here))
1176 // ugh, see chord-tremolo.ly
1177 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1180 b.translate (Offset (tx, -dir * bdy * j));
1181 rightbeams.add_molecule (b);
1186 a = Lookup::beam (dydx, w, thick);
1188 for (; j < rwholebeams + rhalfs; j++)
1191 b.translate_axis (- dir * bdy * j, Y_AXIS);
1192 rightbeams.add_molecule (b);
1196 leftbeams.add_molecule (rightbeams);
1198 /* Does beam quanting think of the asymetry of beams?
1199 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1204 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1206 Beam::brew_molecule (SCM smob)
1208 Grob *me =unsmob_grob (smob);
1211 if (!gh_pair_p (me->get_grob_property ("stems")))
1214 Link_array<Item>stems =
1215 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1216 if (visible_stem_count (me))
1218 // ugh -> use commonx
1219 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1220 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1224 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1225 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1228 SCM posns = me->get_grob_property ("positions");
1230 if (!ly_number_pair_p (posns))
1232 programming_error ("No beam posns");
1233 pos = Interval (0,0);
1236 pos= ly_scm2interval (posns);
1237 Real dy = pos.delta ();
1238 Real dydx = dy && dx ? dy/dx : 0;
1240 for (int i=0; i < stems.size (); i++)
1242 Item *item = stems[i];
1243 Item *prev = (i > 0)? stems[i-1] : 0;
1244 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1246 Molecule sb = stem_beams (me, item, next, prev, dydx);
1247 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1248 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1249 mol.add_molecule (sb);
1252 mol.translate_axis (x0
1253 - dynamic_cast<Spanner*> (me)
1254 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1257 #if (DEBUG_QUANTING)
1260 This code prints the demerits for each beam. Perhaps this
1261 should be switchable for those who want to twiddle with the
1267 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1270 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1273 SCM properties = Font_interface::font_alist_chain (me);
1276 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1277 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1281 return mol.smobbed_copy ();
1285 Beam::forced_stem_count (Grob *me)
1287 Link_array<Item>stems =
1288 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1290 for (int i=0; i < stems.size (); i++)
1294 if (Stem::invisible_b (s))
1297 if (((int)Stem::chord_start_y (s))
1298 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1308 Beam::visible_stem_count (Grob *me)
1310 Link_array<Item>stems =
1311 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1313 for (int i = stems.size (); i--;)
1315 if (!Stem::invisible_b (stems[i]))
1322 Beam::first_visible_stem (Grob *me)
1324 Link_array<Item>stems =
1325 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1327 for (int i = 0; i < stems.size (); i++)
1329 if (!Stem::invisible_b (stems[i]))
1336 Beam::last_visible_stem (Grob *me)
1338 Link_array<Item>stems =
1339 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1340 for (int i = stems.size (); i--;)
1342 if (!Stem::invisible_b (stems[i]))
1352 handle rest under beam (do_post: beams are calculated now)
1353 what about combination of collisions and rest under beam.
1357 rest -> stem -> beam -> interpolate_y_position ()
1359 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1361 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1363 Grob *rest = unsmob_grob (element_smob);
1364 Axis a = (Axis) gh_scm2int (axis);
1366 assert (a == Y_AXIS);
1368 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1371 return gh_double2scm (0.0);
1372 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1374 || !Beam::has_interface (beam)
1375 || !Beam::visible_stem_count (beam))
1376 return gh_double2scm (0.0);
1378 // make callback for rest from this.
1379 // todo: make sure this calced already.
1381 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1382 Interval pos (0, 0);
1383 SCM s = beam->get_grob_property ("positions");
1384 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1385 pos = ly_scm2interval (s);
1387 Real dy = pos.delta ();
1388 // ugh -> use commonx
1389 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1390 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1391 Real dydx = dy && dx ? dy/dx : 0;
1393 Direction d = Stem::get_direction (stem);
1394 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1396 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1399 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1402 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1404 minimum_dist + -d * (beamy - rest_dim) >? 0;
1406 int stafflines = Staff_symbol_referencer::line_count (rest);
1408 // move discretely by half spaces.
1409 int discrete_dist = int (ceil (dist));
1411 // move by whole spaces inside the staff.
1412 if (discrete_dist < stafflines+1)
1413 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1415 return gh_double2scm (-d * discrete_dist);
1421 ADD_INTERFACE (Beam, "beam-interface",
1424 #'thickness= weight of beams, in staffspace
1427 We take the least squares line through the ideal-length stems, and
1428 then damp that using
1430 damped = tanh (slope)
1432 this gives an unquantized left and right position for the beam end.
1433 Then we take all combinations of quantings near these left and right
1434 positions, and give them a score (according to how close they are to
1435 the ideal slope, how close the result is to the ideal stems, etc.). We
1436 take the best scoring combination.
1439 "position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy direction damping flag-width-function neutral-direction positions space-function thickness");