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 int multiplicity = get_multiplicity (me);
78 Real ss = Staff_symbol_referencer::staff_space (me);
80 SCM s = me->get_grob_property ("beam-space");
82 return gh_scm2double (s) * ss;
83 else if (s != SCM_EOL && gh_list_p (s))
84 return gh_scm2double (scm_list_ref (s,
85 gh_int2scm (multiplicity - 1
86 <? scm_ilength (s) - 1)))
89 Real slt = me->paper_l ()->get_var ("linethickness");
90 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) * ss;
92 Real interbeam = multiplicity < 4
93 ? (2*ss + slt - thickness) / 2.0
94 : (3*ss + slt - thickness) / 3.0;
100 Beam::get_multiplicity (Grob *me)
103 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
105 Grob *sc = unsmob_grob (ly_car (s));
107 if (Stem::has_interface (sc))
108 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
113 /* After pre-processing all directions should be set.
114 Several post-processing routines (stem, slur, script) need stem/beam
116 Currenly, this means that beam has set all stem's directions.
117 [Alternatively, stems could set its own directions, according to
118 their beam, during 'final-pre-processing'.] */
119 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
121 Beam::before_line_breaking (SCM smob)
123 Grob *me = unsmob_grob (smob);
125 /* Beams with less than 2 two stems don't make much sense, but could happen
130 For a beam that only has one stem, we try to do some disappearance magic:
131 we revert the flag, and move on to The Eternal Engraving Fields. */
133 int count = visible_stem_count (me);
136 me->warning (_ ("beam has less than two visible stems"));
138 SCM stems = me->get_grob_property ("stems");
139 if (scm_ilength (stems) == 1)
141 me->warning (_ ("Beam has less than two stems. Removing beam."));
143 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
146 return SCM_UNSPECIFIED;
148 else if (scm_ilength (stems) == 0)
151 return SCM_UNSPECIFIED;
156 if (!Directional_element_interface::get (me))
157 Directional_element_interface::set (me, get_default_dir (me));
159 consider_auto_knees (me);
160 set_stem_directions (me);
161 set_stem_shorten (me);
167 Beam::get_default_dir (Grob *me)
169 Drul_array<int> total;
170 total[UP] = total[DOWN] = 0;
171 Drul_array<int> count;
172 count[UP] = count[DOWN] = 0;
175 Link_array<Item> stems=
176 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
178 for (int i=0; i <stems.size (); i++)
181 Direction sd = Directional_element_interface::get (s);
182 int current = sd ? (1 + d * sd)/2
183 : Stem::get_center_distance (s, (Direction)-d);
190 } while (flip (&d) != DOWN);
192 SCM func = me->get_grob_property ("dir-function");
193 SCM s = gh_call2 (func,
194 gh_cons (gh_int2scm (count[UP]),
195 gh_int2scm (count[DOWN])),
196 gh_cons (gh_int2scm (total[UP]),
197 gh_int2scm (total[DOWN])));
199 if (gh_number_p (s) && gh_scm2int (s))
202 /* If dir is not determined: get default */
203 return to_dir (me->get_grob_property ("neutral-direction"));
207 /* Set all stems with non-forced direction to beam direction.
208 Urg: non-forced should become `without/with unforced' direction,
209 once stem gets cleaned-up. */
211 Beam::set_stem_directions (Grob *me)
213 Link_array<Item> stems
214 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
215 Direction d = Directional_element_interface::get (me);
217 for (int i=0; i <stems.size (); i++)
220 SCM force = s->remove_grob_property ("dir-forced");
221 if (!gh_boolean_p (force) || !gh_scm2bool (force))
222 Directional_element_interface::set (s, d);
226 /* Simplistic auto-knees; only consider vertical gap between two
229 `Forced' stem directions are ignored. If you don't want auto-knees,
230 don't set, or unset auto-knee-gap. */
232 Beam::consider_auto_knees (Grob *me)
234 SCM scm = me->get_grob_property ("auto-knee-gap");
236 if (gh_number_p (scm))
240 Real staff_space = Staff_symbol_referencer::staff_space (me);
241 Real gap = gh_scm2double (scm) / staff_space;
243 Direction d = Directional_element_interface::get (me);
244 Link_array<Item> stems=
245 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
247 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
248 for (int i=1; i < stems.size (); i++)
249 if (!Stem::invisible_b (stems[i]))
250 common = common->common_refpoint (stems[i], Y_AXIS);
253 for (int i=1; i < stems.size (); i++)
255 if (!Stem::invisible_b (stems[i-1]))
257 if (Stem::invisible_b (stems[l]))
259 if (Stem::invisible_b (stems[i]))
262 Real left = Stem::extremal_heads (stems[l])[d]
263 ->relative_coordinate (common, Y_AXIS);
264 Real right = Stem::extremal_heads (stems[i])[-d]
265 ->relative_coordinate (common, Y_AXIS);
267 Real dy = right - left;
271 knee_y = (right + left) / 2;
279 for (int i=0; i < stems.size (); i++)
281 if (Stem::invisible_b (stems[i]))
284 Real y = Stem::extremal_heads (stems[i])[d]
285 ->relative_coordinate (common, Y_AXIS);
287 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
288 s->set_grob_property ("dir-forced", SCM_BOOL_T);
294 /* Set stem's shorten property if unset.
297 take some y-position (chord/beam/nearest?) into account
298 scmify forced-fraction
302 why is shorten stored in beam, and not directly in stem?
306 Beam::set_stem_shorten (Grob *m)
308 Spanner*me = dynamic_cast<Spanner*> (m);
310 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
312 int multiplicity = get_multiplicity (me);
314 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
315 if (shorten == SCM_EOL)
318 int sz = scm_ilength (shorten);
320 Real staff_space = Staff_symbol_referencer::staff_space (me);
321 SCM shorten_elt = scm_list_ref (shorten,
322 gh_int2scm (multiplicity <? (sz - 1)));
323 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
325 /* your similar cute comment here */
326 shorten_f *= forced_fraction;
328 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
331 /* Call list of y-dy-callbacks, that handle setting of
332 grob-properties y, dy.
334 User may set grob-properties: y-position-hs and height-hs
335 (to be fixed) that override the calculated y and dy.
337 Because y and dy cannot be calculated and quanted separately, we
338 always calculate both, then check for user override. */
339 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
341 Beam::after_line_breaking (SCM smob)
343 Grob *me = unsmob_grob (smob);
345 /* Copy to mutable list. */
346 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
347 me->set_grob_property ("positions", s);
349 if (ly_car (s) != SCM_BOOL_F)
350 return SCM_UNSPECIFIED;
352 SCM callbacks = me->get_grob_property ("position-callbacks");
353 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
354 gh_call1 (ly_car (i), smob);
356 set_stem_lengths (me);
357 return SCM_UNSPECIFIED;
371 - Make all demerits customisable
373 - One sensible check per demerit (what's this --hwn)
375 - Add demerits for quants per se, as to forbid a specific quant
379 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
381 Beam::quanting (SCM smob)
383 Grob *me = unsmob_grob (smob);
385 SCM s = me->get_grob_property ("positions");
386 Real yl = gh_scm2double (gh_car (s));
387 Real yr = gh_scm2double (gh_cdr (s));
389 Real ss = Staff_symbol_referencer::staff_space (me);
390 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
391 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
394 SCM sdy = me->get_grob_property ("least-squares-dy");
395 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
398 Real sit = (thickness - slt) / 2;
400 Real hang = 1.0 - (thickness - slt) / 2;
401 Real quants [] = {straddle, sit, inter, hang };
403 int num_quants = int (sizeof (quants)/sizeof (Real));
408 going to REGION_SIZE == 2, yields another 0.6 second with
412 (result indexes between 70 and 575) ? --hwn.
416 const int REGION_SIZE = 3;
417 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
418 for (int j = 0; j < num_quants; j++)
420 quantsl.push (i + quants[j] + int (yl));
421 quantsr.push (i + quants[j] + int (yr));
424 Array<Quant_score> qscores;
426 for (int l =0; l < quantsl.size (); l++)
427 for (int r =0; r < quantsr.size (); r++)
439 This is a longish function, but we don't separate this out into
440 neat modular separate subfunctions, as the subfunctions would be
441 called for many values of YL, YR. By precomputing various
442 parameters outside of the loop, we can save a lot of time.
445 for (int i = qscores.size (); i--;)
446 if (qscores[i].demerits < 100)
449 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
453 Real rad = Staff_symbol_referencer::staff_radius (me);
454 int multiplicity = get_multiplicity (me);
455 Real interbeam = multiplicity < 4
456 ? (2*ss + slt - thickness) / 2.0
457 : (3*ss + slt - thickness) / 3.0;
459 for (int i = qscores.size (); i--;)
460 if (qscores[i].demerits < 100)
463 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
464 rad, slt, thickness, interbeam,
470 Do stem lengths. These depend on YL and YR linearly, so we can
471 precompute for every stem 2 factors.
473 Link_array<Grob> stems=
474 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
475 Array<Stem_info> stem_infos;
476 Array<Real> lbase_lengths;
477 Array<Real> rbase_lengths;
479 Array<int> directions;
480 for (int i= 0; i < stems.size(); i++)
483 stem_infos.push( Stem::calc_stem_info (s));
485 Real b = calc_stem_y (me, s, Interval (1,0));
486 lbase_lengths.push (b);
488 b = calc_stem_y (me, s, Interval (0,1));
489 rbase_lengths.push (b);
490 directions.push( Directional_element_interface::get( s));
493 for (int i = qscores.size (); i--;)
494 if (qscores[i].demerits < 100)
497 += score_stem_lengths (stems, stem_infos,
498 lbase_lengths, rbase_lengths,
500 me, qscores[i].yl, qscores[i].yr);
506 for (int i = qscores.size (); i--;)
508 if (qscores[i].demerits < best)
510 best = qscores [i].demerits ;
516 me->set_grob_property ("positions",
517 gh_cons (gh_double2scm (qscores[best_idx].yl),
518 gh_double2scm (qscores[best_idx].yr))
524 me->set_grob_property ("quant-score",
525 gh_double2scm (qscores[best_idx].demerits));
526 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
529 return SCM_UNSPECIFIED;
533 Beam::score_stem_lengths (Link_array<Grob>stems,
534 Array<Stem_info> stem_infos,
535 Array<Real> left_factor,
536 Array<Real> right_factor,
537 Array<int> directions,
538 Grob*me, Real yl, Real yr)
540 Real demerit_score = 0.0 ;
542 for (int i=0; i < stems.size (); i++)
545 if (Stem::invisible_b (s))
549 yl * left_factor[i] + right_factor[i]* yr;
551 Stem_info info = stem_infos[i];
552 Direction d = Direction (directions[i]);
554 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
555 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
557 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
560 demerit_score *= 2.0 /stems.size ();
562 return demerit_score;
566 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
567 Real dy_mus, Real dy_damp)
572 if (sign (dy_damp) != sign (dy))
577 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
580 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
587 return x - floor (x);
591 Beam::score_forbidden_quants (Grob*me,
595 Real thickness, Real interbeam,
601 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
603 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
606 // todo: use multiplicity of outer stems.
607 if (multiplicity >= 2)
611 Real sit = (thickness - slt) / 2;
613 Real hang = 1.0 - (thickness - slt) / 2;
615 Direction dir = Directional_element_interface::get (me);
616 if (fabs (yl - dir * interbeam) < rad
617 && fabs (my_modf (yl) - inter) < 1e-3)
619 if (fabs (yr - dir * interbeam) < rad
620 && fabs (my_modf (yr) - inter) < 1e-3)
626 Can't we simply compute the distance between the nearest
627 staffline and the secondary beam? That would get rid of the
628 silly case analysis here (which is probably not when we have
629 different beam-thicknesses.)
634 // hmm, without Interval/Drul_array, you get ~ 4x same code...
635 if (fabs (yl - dir * interbeam) < rad + inter)
637 if (dir == UP && dy <= eps
638 && fabs (my_modf (yl) - sit) < eps)
641 if (dir == DOWN && dy >= eps
642 && fabs (my_modf (yl) - hang) < eps)
646 if (fabs (yr - dir * interbeam) < rad + inter)
648 if (dir == UP && dy >= eps
649 && fabs (my_modf (yr) - sit) < eps)
652 if (dir == DOWN && dy <= eps
653 && fabs (my_modf (yr) - hang) < eps)
657 if (multiplicity >= 3)
659 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
661 if (dir == UP && dy <= eps
662 && fabs (my_modf (yl) - straddle) < eps)
665 if (dir == DOWN && dy >= eps
666 && fabs (my_modf (yl) - straddle) < eps)
670 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
672 if (dir == UP && dy >= eps
673 && fabs (my_modf (yr) - straddle) < eps)
676 if (dir == DOWN && dy <= eps
677 && fabs (my_modf (yr) - straddle) < eps)
688 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
690 Beam::least_squares (SCM smob)
692 Grob *me = unsmob_grob (smob);
694 int count = visible_stem_count (me);
699 me->set_grob_property ("positions", ly_interval2scm (pos));
700 return SCM_UNSPECIFIED;
703 Direction dir = Directional_element_interface::get (me);
705 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
706 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
710 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
711 Stem::chord_start_f (last_visible_stem (me)));
715 TODO : use scoring for this.
717 complicated, because we take stem-info.ideal for determining
721 /* Make simple beam on middle line have small tilt */
722 if (!ideal[LEFT] && chord.delta () && count == 2)
724 Direction d = (Direction) (sign (chord.delta ()) * dir);
725 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
738 Array<Offset> ideals;
740 // ugh -> use commonx
741 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
742 Link_array<Item> stems=
743 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
745 for (int i=0; i < stems.size (); i++)
748 if (Stem::invisible_b (s))
750 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
751 Stem::calc_stem_info (s).ideal_y));
755 minimise_least_squares (&dydx, &y, ideals);
757 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
759 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
761 pos = Interval (y*dir, (y+dy) * dir);
764 me->set_grob_property ("positions", ly_interval2scm (pos));
765 return SCM_UNSPECIFIED;
768 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
770 Beam::check_concave (SCM smob)
772 Grob *me = unsmob_grob (smob);
774 Link_array<Item> stems =
775 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
777 for (int i = 0; i < stems.size ();)
779 if (Stem::invisible_b (stems[i]))
785 if (stems.size () < 3)
786 return SCM_UNSPECIFIED;
788 Direction dir = Directional_element_interface::get (me);
789 /* Concaveness #1: If distance of an inner notehead to line between
790 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
791 beam is concave (Heinz Stolba). */
792 bool concaveness1 = false;
793 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
796 Real dy = Stem::chord_start_f (stems.top ())
797 - Stem::chord_start_f (stems[0]);
798 Real slope = dy / (stems.size () - 1);
800 Real y0 = Stem::chord_start_f (stems[0]);
801 for (int i = 1; i < stems.size () - 1; i++)
803 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
813 /* Concaveness #2: Sum distances of inner noteheads that fall
814 outside the interval of the two outer noteheads */
815 Real concaveness2 = 0;
816 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
817 if (!concaveness1 && r2 > 0)
820 Interval iv (Stem::chord_start_f (stems[0]),
821 Stem::chord_start_f (stems.top ()));
823 if (iv[MAX] < iv[MIN])
826 for (int i = 1; i < stems.size () - 1; i++)
829 Real f = Stem::chord_start_f (stems[i]);
830 if ((c = f - iv[MAX]) > 0)
832 else if ((c = f - iv[MIN]) < 0)
838 concaveness2 = concave / (stems.size () - 2);
839 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
840 to behave as baerenreiter. */
841 concaveness2 /= (stems.size () - 2);
844 /* TODO: some sort of damping iso -> plain horizontal */
845 if (concaveness1 || concaveness2 > r2)
847 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
848 Real r = pos.linear_combination (0);
849 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
850 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
853 return SCM_UNSPECIFIED;
856 /* This neat trick is by Werner Lemberg,
857 damped = tanh (slope)
858 corresponds with some tables in [Wanske] CHECKME */
859 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
861 Beam::slope_damping (SCM smob)
863 Grob *me = unsmob_grob (smob);
865 if (visible_stem_count (me) <= 1)
866 return SCM_UNSPECIFIED;
868 SCM s = me->get_grob_property ("damping");
869 int damping = gh_scm2int (s);
873 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
874 Real dy = pos.delta ();
876 // ugh -> use commonx
877 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
878 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
879 Real dydx = dy && dx ? dy/dx : 0;
880 dydx = 0.6 * tanh (dydx) / damping;
882 Real damped_dy = dydx * dx;
883 pos[LEFT] += (dy - damped_dy) / 2;
884 pos[RIGHT] -= (dy - damped_dy) / 2;
886 me->set_grob_property ("positions", ly_interval2scm (pos));
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::duration_log (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 ("linethickness");
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 ("linethickness");
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 "beam-space 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 thickness");