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).
28 #include <math.h> // tanh.
30 #include "molecule.hh"
31 #include "directional-element-interface.hh"
35 #include "least-squares.hh"
37 #include "paper-def.hh"
39 #include "group-interface.hh"
40 #include "staff-symbol-referencer.hh"
44 #include "text-item.hh" // debug output.
45 #include "font-interface.hh" // debug output.
48 #define DEBUG_QUANTING 0
52 shrink_extra_weight (Real x)
54 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
58 Beam::add_stem (Grob *me, Grob *s)
60 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
62 s->add_dependency (me);
64 assert (!Stem::beam_l (s));
65 s->set_grob_property ("beam", me->self_scm ());
67 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
72 TODO: fix this for grace notes.
75 Beam::get_interbeam (Grob *me)
77 Real slt = me->paper_l ()->get_var ("stafflinethickness");
78 Real ss = Staff_symbol_referencer::staff_space (me);
79 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
82 int multiplicity = get_multiplicity (me);
83 Real interbeam = multiplicity < 4
84 ? (2*ss + slt - thickness) / 2.0
85 : (3*ss + slt - thickness) / 3.0;
91 Beam::get_multiplicity (Grob *me)
94 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
96 Grob *sc = unsmob_grob (ly_car (s));
98 if (Stem::has_interface (sc))
99 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
104 /* After pre-processing all directions should be set.
105 Several post-processing routines (stem, slur, script) need stem/beam
107 Currenly, this means that beam has set all stem's directions.
108 [Alternatively, stems could set its own directions, according to
109 their beam, during 'final-pre-processing'.] */
110 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
112 Beam::before_line_breaking (SCM smob)
114 Grob *me = unsmob_grob (smob);
116 /* Beams with less than 2 two stems don't make much sense, but could happen
121 For a beam that only has one stem, we try to do some disappearance magic:
122 we revert the flag, and move on to The Eternal Engraving Fields. */
124 int count = visible_stem_count (me);
127 me->warning (_ ("beam has less than two visible stems"));
129 SCM stems = me->get_grob_property ("stems");
130 if (scm_ilength (stems) == 1)
132 me->warning (_ ("Beam has less than two stems. Removing beam."));
134 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
137 return SCM_UNSPECIFIED;
139 else if (scm_ilength (stems) == 0)
142 return SCM_UNSPECIFIED;
147 if (!Directional_element_interface::get (me))
148 Directional_element_interface::set (me, get_default_dir (me));
150 consider_auto_knees (me);
151 set_stem_directions (me);
152 set_stem_shorten (me);
158 Beam::get_default_dir (Grob *me)
160 Drul_array<int> total;
161 total[UP] = total[DOWN] = 0;
162 Drul_array<int> count;
163 count[UP] = count[DOWN] = 0;
166 Link_array<Item> stems=
167 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
169 for (int i=0; i <stems.size (); i++)
172 Direction sd = Directional_element_interface::get (s);
173 int current = sd ? (1 + d * sd)/2
174 : Stem::get_center_distance (s, (Direction)-d);
181 } while (flip (&d) != DOWN);
183 SCM func = me->get_grob_property ("dir-function");
184 SCM s = gh_call2 (func,
185 gh_cons (gh_int2scm (count[UP]),
186 gh_int2scm (count[DOWN])),
187 gh_cons (gh_int2scm (total[UP]),
188 gh_int2scm (total[DOWN])));
190 if (gh_number_p (s) && gh_scm2int (s))
193 /* If dir is not determined: get default */
194 return to_dir (me->get_grob_property ("neutral-direction"));
198 /* Set all stems with non-forced direction to beam direction.
199 Urg: non-forced should become `without/with unforced' direction,
200 once stem gets cleaned-up. */
202 Beam::set_stem_directions (Grob *me)
204 Link_array<Item> stems
205 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
206 Direction d = Directional_element_interface::get (me);
208 for (int i=0; i <stems.size (); i++)
211 SCM force = s->remove_grob_property ("dir-forced");
212 if (!gh_boolean_p (force) || !gh_scm2bool (force))
213 Directional_element_interface::set (s, d);
217 /* Simplistic auto-knees; only consider vertical gap between two
220 `Forced' stem directions are ignored. If you don't want auto-knees,
221 don't set, or unset auto-knee-gap. */
223 Beam::consider_auto_knees (Grob *me)
225 SCM scm = me->get_grob_property ("auto-knee-gap");
227 if (gh_number_p (scm))
231 Real staff_space = Staff_symbol_referencer::staff_space (me);
232 Real gap = gh_scm2double (scm) / staff_space;
234 Direction d = Directional_element_interface::get (me);
235 Link_array<Item> stems=
236 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
238 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
239 for (int i=1; i < stems.size (); i++)
240 if (!Stem::invisible_b (stems[i]))
241 common = common->common_refpoint (stems[i], Y_AXIS);
244 for (int i=1; i < stems.size (); i++)
246 if (!Stem::invisible_b (stems[i-1]))
248 if (Stem::invisible_b (stems[l]))
250 if (Stem::invisible_b (stems[i]))
253 Real left = Stem::extremal_heads (stems[l])[d]
254 ->relative_coordinate (common, Y_AXIS);
255 Real right = Stem::extremal_heads (stems[i])[-d]
256 ->relative_coordinate (common, Y_AXIS);
258 Real dy = right - left;
262 knee_y = (right + left) / 2;
270 for (int i=0; i < stems.size (); i++)
272 if (Stem::invisible_b (stems[i]))
275 Real y = Stem::extremal_heads (stems[i])[d]
276 ->relative_coordinate (common, Y_AXIS);
278 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
279 s->set_grob_property ("dir-forced", SCM_BOOL_T);
285 /* Set stem's shorten property if unset.
288 take some y-position (chord/beam/nearest?) into account
289 scmify forced-fraction
293 why is shorten stored in beam, and not directly in stem?
297 Beam::set_stem_shorten (Grob *m)
299 Spanner*me = dynamic_cast<Spanner*> (m);
301 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
303 int multiplicity = get_multiplicity (me);
305 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
306 if (shorten == SCM_EOL)
309 int sz = scm_ilength (shorten);
311 Real staff_space = Staff_symbol_referencer::staff_space (me);
312 SCM shorten_elt = scm_list_ref (shorten,
313 gh_int2scm (multiplicity <? (sz - 1)));
314 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
316 /* your similar cute comment here */
317 shorten_f *= forced_fraction;
319 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
322 /* Call list of y-dy-callbacks, that handle setting of
323 grob-properties y, dy.
325 User may set grob-properties: y-position-hs and height-hs
326 (to be fixed) that override the calculated y and dy.
328 Because y and dy cannot be calculated and quanted separately, we
329 always calculate both, then check for user override. */
330 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
332 Beam::after_line_breaking (SCM smob)
334 Grob *me = unsmob_grob (smob);
336 /* Copy to mutable list. */
337 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
338 me->set_grob_property ("positions", s);
340 if (ly_car (s) != SCM_BOOL_F)
341 return SCM_UNSPECIFIED;
343 SCM callbacks = me->get_grob_property ("position-callbacks");
344 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
345 gh_call1 (ly_car (i), smob);
347 return SCM_UNSPECIFIED;
361 - Make all demerits customisable
363 - One sensible check per demerit (what's this --hwn)
365 - Add demerits for quants per se, as to forbid a specific quant
369 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
371 Beam::quanting (SCM smob)
373 Grob *me = unsmob_grob (smob);
375 SCM s = me->get_grob_property ("positions");
376 Real yl = gh_scm2double (gh_car (s));
377 Real yr = gh_scm2double (gh_cdr (s));
379 Real ss = Staff_symbol_referencer::staff_space (me);
380 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
381 Real slt = me->paper_l ()->get_var ("stafflinethickness") / ss;
384 SCM sdy = me->get_grob_property ("least-squares-dy");
385 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
388 Real sit = (thickness - slt) / 2;
390 Real hang = 1.0 - (thickness - slt) / 2;
391 Real quants [] = {straddle, sit, inter, hang };
393 int num_quants = int (sizeof (quants)/sizeof (Real));
398 going to REGION_SIZE == 2, yields another 0.6 second with
402 (result indexes between 70 and 575) ? --hwn.
406 const int REGION_SIZE = 3;
407 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
408 for (int j = 0; j < num_quants; j++)
410 quantsl.push (i + quants[j] + int (yl));
411 quantsr.push (i + quants[j] + int (yr));
414 Array<Quant_score> qscores;
416 for (int l =0; l < quantsl.size (); l++)
417 for (int r =0; r < quantsr.size (); r++)
429 This is a longish function, but we don't separate this out into
430 neat modular separate subfunctions, as the subfunctions would be
431 called for many values of YL, YR. By precomputing various
432 parameters outside of the loop, we can save a lot of time.
435 for (int i = qscores.size (); i--;)
436 if (qscores[i].demerits < 100)
439 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
443 Real rad = Staff_symbol_referencer::staff_radius (me);
444 int multiplicity = get_multiplicity (me);
445 Real interbeam = multiplicity < 4
446 ? (2*ss + slt - thickness) / 2.0
447 : (3*ss + slt - thickness) / 3.0;
449 for (int i = qscores.size (); i--;)
450 if (qscores[i].demerits < 100)
453 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
454 rad, slt, thickness, interbeam,
460 Do stem lengths. These depend on YL and YR linearly, so we can
461 precompute for every stem 2 factors.
463 Link_array<Grob> stems=
464 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
465 Array<Stem_info> stem_infos;
466 Array<Real> lbase_lengths;
467 Array<Real> rbase_lengths;
469 Array<int> directions;
470 for (int i= 0; i < stems.size(); i++)
473 stem_infos.push( Stem::calc_stem_info (s));
475 Real b = calc_stem_y (me, s, Interval (1,0));
476 lbase_lengths.push (b);
478 b = calc_stem_y (me, s, Interval (0,1));
479 rbase_lengths.push (b);
480 directions.push( Directional_element_interface::get( s));
483 for (int i = qscores.size (); i--;)
484 if (qscores[i].demerits < 100)
487 += score_stem_lengths (stems, stem_infos,
488 lbase_lengths, rbase_lengths,
490 me, qscores[i].yl, qscores[i].yr);
496 for (int i = qscores.size (); i--;)
498 if (qscores[i].demerits < best)
500 best = qscores [i].demerits ;
506 me->set_grob_property ("positions",
507 gh_cons (gh_double2scm (qscores[best_idx].yl),
508 gh_double2scm (qscores[best_idx].yr))
514 me->set_grob_property ("quant-score",
515 gh_double2scm (qscores[best_idx].demerits));
516 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
519 return SCM_UNSPECIFIED;
523 Beam::score_stem_lengths (Link_array<Grob>stems,
524 Array<Stem_info> stem_infos,
525 Array<Real> left_factor,
526 Array<Real> right_factor,
527 Array<int> directions,
528 Grob*me, Real yl, Real yr)
530 Real demerit_score = 0.0 ;
532 for (int i=0; i < stems.size (); i++)
535 if (Stem::invisible_b (s))
539 yl * left_factor[i] + right_factor[i]* yr;
541 Stem_info info = stem_infos[i];
542 Direction d = Direction (directions[i]);
544 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
545 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
547 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
550 demerit_score *= 2.0 /stems.size ();
552 return demerit_score;
556 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
557 Real dy_mus, Real dy_damp)
562 if (sign (dy_damp) != sign (dy))
567 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
570 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
577 return x - floor (x);
581 Beam::score_forbidden_quants (Grob*me,
585 Real thickness, Real interbeam,
591 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
593 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
596 // todo: use multiplicity of outer stems.
597 if (multiplicity >= 2)
601 Real sit = (thickness - slt) / 2;
603 Real hang = 1.0 - (thickness - slt) / 2;
605 Direction dir = Directional_element_interface::get (me);
606 if (fabs (yl - dir * interbeam) < rad
607 && fabs (my_modf (yl) - inter) < 1e-3)
609 if (fabs (yr - dir * interbeam) < rad
610 && fabs (my_modf (yr) - inter) < 1e-3)
616 Can't we simply compute the distance between the nearest
617 staffline and the secondary beam? That would get rid of the
618 silly case analysis here (which is probably not when we have
619 different beam-thicknesses.)
624 // hmm, without Interval/Drul_array, you get ~ 4x same code...
625 if (fabs (yl - dir * interbeam) < rad + inter)
627 if (dir == UP && dy <= eps
628 && fabs (my_modf (yl) - sit) < eps)
631 if (dir == DOWN && dy >= eps
632 && fabs (my_modf (yl) - hang) < eps)
636 if (fabs (yr - dir * interbeam) < rad + inter)
638 if (dir == UP && dy >= eps
639 && fabs (my_modf (yr) - sit) < eps)
642 if (dir == DOWN && dy <= eps
643 && fabs (my_modf (yr) - hang) < eps)
647 if (multiplicity >= 3)
649 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
651 if (dir == UP && dy <= eps
652 && fabs (my_modf (yl) - straddle) < eps)
655 if (dir == DOWN && dy >= eps
656 && fabs (my_modf (yl) - straddle) < eps)
660 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
662 if (dir == UP && dy >= eps
663 && fabs (my_modf (yr) - straddle) < eps)
666 if (dir == DOWN && dy <= eps
667 && fabs (my_modf (yr) - straddle) < eps)
678 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
680 Beam::least_squares (SCM smob)
682 Grob *me = unsmob_grob (smob);
684 int count = visible_stem_count (me);
689 me->set_grob_property ("positions", ly_interval2scm (pos));
690 return SCM_UNSPECIFIED;
693 Direction dir = Directional_element_interface::get (me);
695 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
696 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
700 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
701 Stem::chord_start_f (last_visible_stem (me)));
705 TODO : use scoring for this.
707 complicated, because we take stem-info.ideal for determining
711 /* Make simple beam on middle line have small tilt */
712 if (!ideal[LEFT] && chord.delta () && count == 2)
714 Direction d = (Direction) (sign (chord.delta ()) * dir);
715 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
728 Array<Offset> ideals;
730 // ugh -> use commonx
731 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
732 Link_array<Item> stems=
733 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
735 for (int i=0; i < stems.size (); i++)
738 if (Stem::invisible_b (s))
740 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
741 Stem::calc_stem_info (s).ideal_y));
745 minimise_least_squares (&dydx, &y, ideals);
747 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
749 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
751 pos = Interval (y*dir, (y+dy) * dir);
754 me->set_grob_property ("positions", ly_interval2scm (pos));
755 return SCM_UNSPECIFIED;
758 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
760 Beam::check_concave (SCM smob)
762 Grob *me = unsmob_grob (smob);
764 Link_array<Item> stems =
765 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
767 for (int i = 0; i < stems.size ();)
769 if (Stem::invisible_b (stems[i]))
775 if (stems.size () < 3)
776 return SCM_UNSPECIFIED;
778 Direction dir = Directional_element_interface::get (me);
779 /* Concaveness #1: If distance of an inner notehead to line between
780 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
781 beam is concave (Heinz Stolba). */
782 bool concaveness1 = false;
783 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
786 Real dy = Stem::chord_start_f (stems.top ())
787 - Stem::chord_start_f (stems[0]);
788 Real slope = dy / (stems.size () - 1);
790 Real y0 = Stem::chord_start_f (stems[0]);
791 for (int i = 1; i < stems.size () - 1; i++)
793 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
803 /* Concaveness #2: Sum distances of inner noteheads that fall
804 outside the interval of the two outer noteheads */
805 Real concaveness2 = 0;
806 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
807 if (!concaveness1 && r2 > 0)
810 Interval iv (Stem::chord_start_f (stems[0]),
811 Stem::chord_start_f (stems.top ()));
813 if (iv[MAX] < iv[MIN])
816 for (int i = 1; i < stems.size () - 1; i++)
819 Real f = Stem::chord_start_f (stems[i]);
820 if ((c = f - iv[MAX]) > 0)
822 else if ((c = f - iv[MIN]) < 0)
828 concaveness2 = concave / (stems.size () - 2);
829 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
830 to behave as baerenreiter. */
831 concaveness2 /= (stems.size () - 2);
834 /* TODO: some sort of damping iso -> plain horizontal */
835 if (concaveness1 || concaveness2 > r2)
837 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
838 Real r = pos.linear_combination (0);
839 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
840 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
843 return SCM_UNSPECIFIED;
846 /* This neat trick is by Werner Lemberg,
847 damped = tanh (slope)
848 corresponds with some tables in [Wanske] CHECKME */
849 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
851 Beam::slope_damping (SCM smob)
853 Grob *me = unsmob_grob (smob);
855 if (visible_stem_count (me) <= 1)
856 return SCM_UNSPECIFIED;
858 SCM s = me->get_grob_property ("damping");
859 int damping = gh_scm2int (s);
863 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
864 Real dy = pos.delta ();
866 // ugh -> use commonx
867 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
868 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
869 Real dydx = dy && dx ? dy/dx : 0;
870 dydx = 0.6 * tanh (dydx) / damping;
872 Real damped_dy = dydx * dx;
873 pos[LEFT] += (dy - damped_dy) / 2;
874 pos[RIGHT] -= (dy - damped_dy) / 2;
876 me->set_grob_property ("positions", ly_interval2scm (pos));
878 return SCM_UNSPECIFIED;
881 MAKE_SCHEME_CALLBACK (Beam, end_after_line_breaking, 1);
883 Beam::end_after_line_breaking (SCM smob)
885 Grob *me = unsmob_grob (smob);
886 set_stem_lengths (me);
888 return SCM_UNSPECIFIED;
892 Calculate the Y position of the stem-end, given the Y-left, Y-right
893 in POS, and for stem S.
896 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
898 int beam_multiplicity = get_multiplicity (me);
899 int stem_multiplicity = (Stem::flag_i (s) - 2) >? 0;
901 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
902 Real interbeam = get_interbeam (me);
904 // ugh -> use commonx
905 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
906 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
907 Real dy = pos.delta ();
908 Real stem_y = (dy && dx
909 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
914 Direction dir = Directional_element_interface::get (me);
915 Direction sdir = Directional_element_interface::get (s);
920 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
922 // huh, why not for first visible?
924 Grob *last_visible = last_visible_stem (me);
927 if ( Staff_symbol_referencer::staff_symbol_l (s)
928 != Staff_symbol_referencer::staff_symbol_l (last_visible))
929 stem_y += Directional_element_interface::get (me)
930 * (beam_multiplicity - stem_multiplicity) * interbeam;
933 programming_error ("No last visible stem");
940 Hmm. At this time, beam position and slope are determined. Maybe,
941 stem directions and length should set to relative to the chord's
942 position of the beam. */
944 Beam::set_stem_lengths (Grob *me)
946 Link_array<Item> stems=
947 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
949 if (stems.size () <= 1)
952 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
953 for (int i=1; i < stems.size (); i++)
954 if (!Stem::invisible_b (stems[i]))
955 common = common->common_refpoint (stems[i], Y_AXIS);
957 Direction dir = Directional_element_interface::get (me);
958 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
959 Real staff_space = Staff_symbol_referencer::staff_space (me);
960 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
961 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
962 for (int i=0; i < stems.size (); i++)
965 if (Stem::invisible_b (s))
968 Real stem_y = calc_stem_y (me, s, pos);
970 // doesn't play well with dvips
972 if (Stem::get_direction (s) == dir)
973 stem_y += Stem::get_direction (s) * thick / 2;
975 /* caution: stem measures in staff-positions */
976 Real id = me->relative_coordinate (common, Y_AXIS)
977 - stems[i]->relative_coordinate (common, Y_AXIS);
978 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
983 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
985 Link_array<Grob> stems=
986 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
989 for (int i=0; i < stems.size (); i++)
993 /* Don't overwrite user override (?) */
994 if (Stem::beam_count (stems[i], d) == -1
995 /* Don't set beaming for outside of outer stems */
996 && ! (d == LEFT && i == 0)
997 && ! (d == RIGHT && i == stems.size () -1))
999 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1000 Stem::set_beaming (stems[i], b, d);
1003 while (flip (&d) != LEFT);
1010 beams to go with one stem.
1015 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1017 // ugh -> use commonx
1019 && ! (next->relative_coordinate (0, X_AXIS)
1020 > here->relative_coordinate (0, X_AXIS)))
1022 && ! (prev->relative_coordinate (0, X_AXIS)
1023 < here->relative_coordinate (0, X_AXIS))))
1024 programming_error ("Beams are not left-to-right");
1026 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1027 Real bdy = get_interbeam (me);
1030 Molecule rightbeams;
1033 if (!Stem::first_head (here))
1036 int t = Stem::type_i (here);
1038 SCM proc = me->get_grob_property ("flag-width-function");
1039 SCM result = gh_call1 (proc, gh_int2scm (t));
1040 nw_f = gh_scm2double (result);
1044 Direction dir = Directional_element_interface::get (me);
1046 /* [Tremolo] beams on whole notes may not have direction set? */
1048 dir = Directional_element_interface::get (here);
1051 /* half beams extending to the left. */
1054 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1055 - Stem::beam_count (prev, RIGHT);
1056 int lwholebeams= Stem::beam_count (here, LEFT)
1057 <? Stem::beam_count (prev, RIGHT);
1059 /* Half beam should be one note-width,
1060 but let's make sure two half-beams never touch */
1062 // FIXME: TODO (check) stem width / sloped beams
1063 Real w = here->relative_coordinate (0, X_AXIS)
1064 - prev->relative_coordinate (0, X_AXIS);
1065 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1067 * me->paper_l ()->get_var ("stafflinethickness");
1071 if (lhalfs) // generates warnings if not
1072 a = Lookup::beam (dydx, w + stem_w, thick);
1073 a.translate (Offset (-w, -w * dydx));
1074 a.translate_axis (-stem_w/2, X_AXIS);
1075 for (int j = 0; j < lhalfs; j++)
1078 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1079 leftbeams.add_molecule (b);
1085 int rhalfs = Stem::beam_count (here, RIGHT)
1086 - Stem::beam_count (next, LEFT);
1087 int rwholebeams= Stem::beam_count (here, RIGHT)
1088 <? Stem::beam_count (next, LEFT);
1090 Real w = next->relative_coordinate (0, X_AXIS)
1091 - here->relative_coordinate (0, X_AXIS);
1093 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1095 * me->paper_l ()->get_var ("stafflinethickness");
1097 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1098 a.translate_axis (- stem_w/2, X_AXIS);
1102 SCM gap = me->get_grob_property ("gap");
1103 if (gh_number_p (gap))
1105 int gap_i = gh_scm2int ((gap));
1106 int nogap = rwholebeams - gap_i;
1108 for (; j < nogap; j++)
1111 b.translate_axis (-dir * bdy * j, Y_AXIS);
1112 rightbeams.add_molecule (b);
1114 if (Stem::invisible_b (here))
1119 a = Lookup::beam (dydx, w + stem_w, thick);
1122 for (; j < rwholebeams; j++)
1126 if (Stem::invisible_b (here))
1127 // ugh, see chord-tremolo.ly
1128 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1131 b.translate (Offset (tx, -dir * bdy * j));
1132 rightbeams.add_molecule (b);
1137 a = Lookup::beam (dydx, w, thick);
1139 for (; j < rwholebeams + rhalfs; j++)
1142 b.translate_axis (- dir * bdy * j, Y_AXIS);
1143 rightbeams.add_molecule (b);
1147 leftbeams.add_molecule (rightbeams);
1149 /* Does beam quanting think of the asymetry of beams?
1150 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1155 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1157 Beam::brew_molecule (SCM smob)
1159 Grob *me =unsmob_grob (smob);
1162 if (!gh_pair_p (me->get_grob_property ("stems")))
1165 Link_array<Item>stems =
1166 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1167 if (visible_stem_count (me))
1169 // ugh -> use commonx
1170 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1171 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1175 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1176 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1179 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1180 Real dy = pos.delta ();
1181 Real dydx = dy && dx ? dy/dx : 0;
1183 for (int i=0; i < stems.size (); i++)
1185 Item *item = stems[i];
1186 Item *prev = (i > 0)? stems[i-1] : 0;
1187 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1189 Molecule sb = stem_beams (me, item, next, prev, dydx);
1190 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1191 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1192 mol.add_molecule (sb);
1195 mol.translate_axis (x0
1196 - dynamic_cast<Spanner*> (me)
1197 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1203 This code prints the demerits for each beam. Perhaps this
1204 should be switchable for those who want to twiddle with the
1210 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1213 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1216 SCM properties = Font_interface::font_alist_chain (me);
1218 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1219 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1222 return mol.smobbed_copy ();
1226 Beam::forced_stem_count (Grob *me)
1228 Link_array<Item>stems =
1229 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1231 for (int i=0; i < stems.size (); i++)
1235 if (Stem::invisible_b (s))
1238 if (((int)Stem::chord_start_f (s))
1239 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1249 Beam::visible_stem_count (Grob *me)
1251 Link_array<Item>stems =
1252 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1254 for (int i = stems.size (); i--;)
1256 if (!Stem::invisible_b (stems[i]))
1263 Beam::first_visible_stem (Grob *me)
1265 Link_array<Item>stems =
1266 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1268 for (int i = 0; i < stems.size (); i++)
1270 if (!Stem::invisible_b (stems[i]))
1277 Beam::last_visible_stem (Grob *me)
1279 Link_array<Item>stems =
1280 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1281 for (int i = stems.size (); i--;)
1283 if (!Stem::invisible_b (stems[i]))
1293 handle rest under beam (do_post: beams are calculated now)
1294 what about combination of collisions and rest under beam.
1298 rest -> stem -> beam -> interpolate_y_position ()
1300 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1302 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1304 Grob *rest = unsmob_grob (element_smob);
1305 Axis a = (Axis) gh_scm2int (axis);
1307 assert (a == Y_AXIS);
1309 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1312 return gh_double2scm (0.0);
1313 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1315 || !Beam::has_interface (beam)
1316 || !Beam::visible_stem_count (beam))
1317 return gh_double2scm (0.0);
1319 // make callback for rest from this.
1320 // todo: make sure this calced already.
1322 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1323 Interval pos (0, 0);
1324 SCM s = beam->get_grob_property ("positions");
1325 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1326 pos = ly_scm2interval (s);
1328 Real dy = pos.delta ();
1329 // ugh -> use commonx
1330 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1331 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1332 Real dydx = dy && dx ? dy/dx : 0;
1334 Direction d = Stem::get_direction (stem);
1335 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1337 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1340 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1343 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1345 minimum_dist + -d * (beamy - rest_dim) >? 0;
1347 int stafflines = Staff_symbol_referencer::line_count (rest);
1349 // move discretely by half spaces.
1350 int discrete_dist = int (ceil (dist));
1352 // move by whole spaces inside the staff.
1353 if (discrete_dist < stafflines+1)
1354 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1356 return gh_double2scm (-d * discrete_dist);
1361 Beam::has_interface (Grob *me)
1363 return me->has_interface (ly_symbol2scm ("beam-interface"));
1367 ADD_INTERFACE (Beam, "beam-interface",
1370 #'thickness= weight of beams, in staffspace
1373 We take the least squares line through the ideal-length stems, and
1374 then damp that using
1376 damped = tanh (slope)
1378 this gives an unquantized left and right position for the beam end.
1379 Then we take all combinations of quantings near these left and right
1380 positions, and give them a score (according to how close they are to
1381 the ideal slope, how close the result is to the ideal stems, etc.). We
1382 take the best scoring combination.
1385 "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 thickness");