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 // one wonders if such genericity is necessary --hwn.
353 SCM callbacks = me->get_grob_property ("position-callbacks");
354 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
355 gh_call1 (ly_car (i), smob);
357 set_stem_lengths (me);
358 return SCM_UNSPECIFIED;
372 - Make all demerits customisable
374 - One sensible check per demerit (what's this --hwn)
376 - Add demerits for quants per se, as to forbid a specific quant
380 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
382 Beam::quanting (SCM smob)
384 Grob *me = unsmob_grob (smob);
386 SCM s = me->get_grob_property ("positions");
387 Real yl = gh_scm2double (gh_car (s));
388 Real yr = gh_scm2double (gh_cdr (s));
390 Real ss = Staff_symbol_referencer::staff_space (me);
391 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
392 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
395 SCM sdy = me->get_grob_property ("least-squares-dy");
396 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
399 Real sit = (thickness - slt) / 2;
401 Real hang = 1.0 - (thickness - slt) / 2;
402 Real quants [] = {straddle, sit, inter, hang };
404 int num_quants = int (sizeof (quants)/sizeof (Real));
409 going to REGION_SIZE == 2, yields another 0.6 second with
413 (result indexes between 70 and 575) ? --hwn.
417 const int REGION_SIZE = 3;
418 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
419 for (int j = 0; j < num_quants; j++)
421 quantsl.push (i + quants[j] + int (yl));
422 quantsr.push (i + quants[j] + int (yr));
425 Array<Quant_score> qscores;
427 for (int l =0; l < quantsl.size (); l++)
428 for (int r =0; r < quantsr.size (); r++)
440 This is a longish function, but we don't separate this out into
441 neat modular separate subfunctions, as the subfunctions would be
442 called for many values of YL, YR. By precomputing various
443 parameters outside of the loop, we can save a lot of time.
446 for (int i = qscores.size (); i--;)
447 if (qscores[i].demerits < 100)
450 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
454 Real rad = Staff_symbol_referencer::staff_radius (me);
455 int multiplicity = get_multiplicity (me);
456 Real interbeam = multiplicity < 4
457 ? (2*ss + slt - thickness) / 2.0
458 : (3*ss + slt - thickness) / 3.0;
460 for (int i = qscores.size (); i--;)
461 if (qscores[i].demerits < 100)
464 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
465 rad, slt, thickness, interbeam,
471 Do stem lengths. These depend on YL and YR linearly, so we can
472 precompute for every stem 2 factors.
474 Link_array<Grob> stems=
475 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
476 Array<Stem_info> stem_infos;
477 Array<Real> lbase_lengths;
478 Array<Real> rbase_lengths;
480 Array<int> directions;
481 for (int i= 0; i < stems.size(); i++)
484 stem_infos.push( Stem::calc_stem_info (s));
486 Real b = calc_stem_y (me, s, Interval (1,0));
487 lbase_lengths.push (b);
489 b = calc_stem_y (me, s, Interval (0,1));
490 rbase_lengths.push (b);
491 directions.push( Directional_element_interface::get( s));
494 for (int i = qscores.size (); i--;)
495 if (qscores[i].demerits < 100)
498 += score_stem_lengths (stems, stem_infos,
499 lbase_lengths, rbase_lengths,
501 me, qscores[i].yl, qscores[i].yr);
507 for (int i = qscores.size (); i--;)
509 if (qscores[i].demerits < best)
511 best = qscores [i].demerits ;
517 me->set_grob_property ("positions",
518 gh_cons (gh_double2scm (qscores[best_idx].yl),
519 gh_double2scm (qscores[best_idx].yr))
525 me->set_grob_property ("quant-score",
526 gh_double2scm (qscores[best_idx].demerits));
527 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
530 return SCM_UNSPECIFIED;
534 Beam::score_stem_lengths (Link_array<Grob>stems,
535 Array<Stem_info> stem_infos,
536 Array<Real> left_factor,
537 Array<Real> right_factor,
538 Array<int> directions,
539 Grob*me, Real yl, Real yr)
541 Real demerit_score = 0.0 ;
543 for (int i=0; i < stems.size (); i++)
546 if (Stem::invisible_b (s))
550 yl * left_factor[i] + right_factor[i]* yr;
552 Stem_info info = stem_infos[i];
553 Direction d = Direction (directions[i]);
555 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
556 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
558 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
561 demerit_score *= 2.0 /stems.size ();
563 return demerit_score;
567 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
568 Real dy_mus, Real dy_damp)
573 if (sign (dy_damp) != sign (dy))
578 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
581 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
588 return x - floor (x);
592 Beam::score_forbidden_quants (Grob*me,
596 Real thickness, Real interbeam,
602 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
604 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
607 // todo: use multiplicity of outer stems.
608 if (multiplicity >= 2)
612 Real sit = (thickness - slt) / 2;
614 Real hang = 1.0 - (thickness - slt) / 2;
616 Direction dir = Directional_element_interface::get (me);
617 if (fabs (yl - dir * interbeam) < rad
618 && fabs (my_modf (yl) - inter) < 1e-3)
620 if (fabs (yr - dir * interbeam) < rad
621 && fabs (my_modf (yr) - inter) < 1e-3)
627 Can't we simply compute the distance between the nearest
628 staffline and the secondary beam? That would get rid of the
629 silly case analysis here (which is probably not when we have
630 different beam-thicknesses.)
635 // hmm, without Interval/Drul_array, you get ~ 4x same code...
636 if (fabs (yl - dir * interbeam) < rad + inter)
638 if (dir == UP && dy <= eps
639 && fabs (my_modf (yl) - sit) < eps)
642 if (dir == DOWN && dy >= eps
643 && fabs (my_modf (yl) - hang) < eps)
647 if (fabs (yr - dir * interbeam) < rad + inter)
649 if (dir == UP && dy >= eps
650 && fabs (my_modf (yr) - sit) < eps)
653 if (dir == DOWN && dy <= eps
654 && fabs (my_modf (yr) - hang) < eps)
658 if (multiplicity >= 3)
660 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
662 if (dir == UP && dy <= eps
663 && fabs (my_modf (yl) - straddle) < eps)
666 if (dir == DOWN && dy >= eps
667 && fabs (my_modf (yl) - straddle) < eps)
671 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
673 if (dir == UP && dy >= eps
674 && fabs (my_modf (yr) - straddle) < eps)
677 if (dir == DOWN && dy <= eps
678 && fabs (my_modf (yr) - straddle) < eps)
689 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
691 Beam::least_squares (SCM smob)
693 Grob *me = unsmob_grob (smob);
695 int count = visible_stem_count (me);
700 me->set_grob_property ("positions", ly_interval2scm (pos));
701 return SCM_UNSPECIFIED;
704 Direction dir = Directional_element_interface::get (me);
706 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
707 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
711 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
712 Stem::chord_start_f (last_visible_stem (me)));
716 TODO : use scoring for this.
718 complicated, because we take stem-info.ideal for determining
722 /* Make simple beam on middle line have small tilt */
723 if (!ideal[LEFT] && chord.delta () && count == 2)
725 Direction d = (Direction) (sign (chord.delta ()) * dir);
726 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
739 Array<Offset> ideals;
741 // ugh -> use commonx
742 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
743 Link_array<Item> stems=
744 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
746 for (int i=0; i < stems.size (); i++)
749 if (Stem::invisible_b (s))
751 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
752 Stem::calc_stem_info (s).ideal_y));
756 minimise_least_squares (&dydx, &y, ideals);
758 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
760 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
762 pos = Interval (y*dir, (y+dy) * dir);
765 me->set_grob_property ("positions", ly_interval2scm (pos));
766 return SCM_UNSPECIFIED;
769 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
771 Beam::check_concave (SCM smob)
773 Grob *me = unsmob_grob (smob);
775 Link_array<Item> stems =
776 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
778 for (int i = 0; i < stems.size ();)
780 if (Stem::invisible_b (stems[i]))
786 if (stems.size () < 3)
787 return SCM_UNSPECIFIED;
789 Direction dir = Directional_element_interface::get (me);
790 /* Concaveness #1: If distance of an inner notehead to line between
791 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
792 beam is concave (Heinz Stolba). */
793 bool concaveness1 = false;
794 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
797 Real dy = Stem::chord_start_f (stems.top ())
798 - Stem::chord_start_f (stems[0]);
799 Real slope = dy / (stems.size () - 1);
801 Real y0 = Stem::chord_start_f (stems[0]);
802 for (int i = 1; i < stems.size () - 1; i++)
804 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
814 /* Concaveness #2: Sum distances of inner noteheads that fall
815 outside the interval of the two outer noteheads */
816 Real concaveness2 = 0;
817 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
818 if (!concaveness1 && r2 > 0)
821 Interval iv (Stem::chord_start_f (stems[0]),
822 Stem::chord_start_f (stems.top ()));
824 if (iv[MAX] < iv[MIN])
827 for (int i = 1; i < stems.size () - 1; i++)
830 Real f = Stem::chord_start_f (stems[i]);
831 if ((c = f - iv[MAX]) > 0)
833 else if ((c = f - iv[MIN]) < 0)
839 concaveness2 = concave / (stems.size () - 2);
840 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
841 to behave as baerenreiter. */
842 concaveness2 /= (stems.size () - 2);
845 /* TODO: some sort of damping iso -> plain horizontal */
846 if (concaveness1 || concaveness2 > r2)
848 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
849 Real r = pos.linear_combination (0);
850 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
851 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
854 return SCM_UNSPECIFIED;
857 /* This neat trick is by Werner Lemberg,
858 damped = tanh (slope)
859 corresponds with some tables in [Wanske] CHECKME */
860 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
862 Beam::slope_damping (SCM smob)
864 Grob *me = unsmob_grob (smob);
866 if (visible_stem_count (me) <= 1)
867 return SCM_UNSPECIFIED;
869 SCM s = me->get_grob_property ("damping");
870 int damping = gh_scm2int (s);
874 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
875 Real dy = pos.delta ();
877 // ugh -> use commonx
878 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
879 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
880 Real dydx = dy && dx ? dy/dx : 0;
881 dydx = 0.6 * tanh (dydx) / damping;
883 Real damped_dy = dydx * dx;
884 pos[LEFT] += (dy - damped_dy) / 2;
885 pos[RIGHT] -= (dy - damped_dy) / 2;
887 me->set_grob_property ("positions", ly_interval2scm (pos));
889 return SCM_UNSPECIFIED;
893 Calculate the Y position of the stem-end, given the Y-left, Y-right
894 in POS, and for stem S.
897 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
899 int beam_multiplicity = get_multiplicity (me);
900 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
902 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
903 Real interbeam = get_interbeam (me);
905 // ugh -> use commonx
906 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
907 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
908 Real dy = pos.delta ();
909 Real stem_y = (dy && dx
910 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
915 Direction dir = Directional_element_interface::get (me);
916 Direction sdir = Directional_element_interface::get (s);
921 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
923 // huh, why not for first visible?
925 Grob *last_visible = last_visible_stem (me);
928 if ( Staff_symbol_referencer::staff_symbol_l (s)
929 != Staff_symbol_referencer::staff_symbol_l (last_visible))
930 stem_y += Directional_element_interface::get (me)
931 * (beam_multiplicity - stem_multiplicity) * interbeam;
934 programming_error ("No last visible stem");
941 Hmm. At this time, beam position and slope are determined. Maybe,
942 stem directions and length should set to relative to the chord's
943 position of the beam. */
945 Beam::set_stem_lengths (Grob *me)
947 Link_array<Item> stems=
948 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
950 if (stems.size () <= 1)
953 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
954 for (int i=1; i < stems.size (); i++)
955 if (!Stem::invisible_b (stems[i]))
956 common = common->common_refpoint (stems[i], Y_AXIS);
958 Direction dir = Directional_element_interface::get (me);
959 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
960 Real staff_space = Staff_symbol_referencer::staff_space (me);
961 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
962 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
963 for (int i=0; i < stems.size (); i++)
966 if (Stem::invisible_b (s))
969 Real stem_y = calc_stem_y (me, s, pos);
971 // doesn't play well with dvips
973 if (Stem::get_direction (s) == dir)
974 stem_y += Stem::get_direction (s) * thick / 2;
976 /* caution: stem measures in staff-positions */
977 Real id = me->relative_coordinate (common, Y_AXIS)
978 - stems[i]->relative_coordinate (common, Y_AXIS);
979 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
984 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
986 Link_array<Grob> stems=
987 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
990 for (int i=0; i < stems.size (); i++)
994 /* Don't overwrite user override (?) */
995 if (Stem::beam_count (stems[i], d) == -1
996 /* Don't set beaming for outside of outer stems */
997 && ! (d == LEFT && i == 0)
998 && ! (d == RIGHT && i == stems.size () -1))
1000 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1001 Stem::set_beaming (stems[i], b, d);
1004 while (flip (&d) != LEFT);
1011 beams to go with one stem.
1016 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1018 // ugh -> use commonx
1020 && ! (next->relative_coordinate (0, X_AXIS)
1021 > here->relative_coordinate (0, X_AXIS)))
1023 && ! (prev->relative_coordinate (0, X_AXIS)
1024 < here->relative_coordinate (0, X_AXIS))))
1025 programming_error ("Beams are not left-to-right");
1027 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1028 Real bdy = get_interbeam (me);
1031 Molecule rightbeams;
1034 if (!Stem::first_head (here))
1037 int t = Stem::type_i (here);
1039 SCM proc = me->get_grob_property ("flag-width-function");
1040 SCM result = gh_call1 (proc, gh_int2scm (t));
1041 nw_f = gh_scm2double (result);
1045 Direction dir = Directional_element_interface::get (me);
1047 /* [Tremolo] beams on whole notes may not have direction set? */
1049 dir = Directional_element_interface::get (here);
1052 /* half beams extending to the left. */
1055 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1056 - Stem::beam_count (prev, RIGHT);
1057 int lwholebeams= Stem::beam_count (here, LEFT)
1058 <? Stem::beam_count (prev, RIGHT);
1060 /* Half beam should be one note-width,
1061 but let's make sure two half-beams never touch */
1063 // FIXME: TODO (check) stem width / sloped beams
1064 Real w = here->relative_coordinate (0, X_AXIS)
1065 - prev->relative_coordinate (0, X_AXIS);
1066 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1068 * me->paper_l ()->get_var ("linethickness");
1072 if (lhalfs) // generates warnings if not
1073 a = Lookup::beam (dydx, w + stem_w, thick);
1074 a.translate (Offset (-w, -w * dydx));
1075 a.translate_axis (-stem_w/2, X_AXIS);
1076 for (int j = 0; j < lhalfs; j++)
1079 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1080 leftbeams.add_molecule (b);
1086 int rhalfs = Stem::beam_count (here, RIGHT)
1087 - Stem::beam_count (next, LEFT);
1088 int rwholebeams= Stem::beam_count (here, RIGHT)
1089 <? Stem::beam_count (next, LEFT);
1091 Real w = next->relative_coordinate (0, X_AXIS)
1092 - here->relative_coordinate (0, X_AXIS);
1094 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1096 * me->paper_l ()->get_var ("linethickness");
1098 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1099 a.translate_axis (- stem_w/2, X_AXIS);
1103 SCM gap = me->get_grob_property ("gap");
1104 if (gh_number_p (gap))
1106 int gap_i = gh_scm2int ((gap));
1107 int nogap = rwholebeams - gap_i;
1109 for (; j < nogap; j++)
1112 b.translate_axis (-dir * bdy * j, Y_AXIS);
1113 rightbeams.add_molecule (b);
1115 if (Stem::invisible_b (here))
1120 a = Lookup::beam (dydx, w + stem_w, thick);
1123 for (; j < rwholebeams; j++)
1127 if (Stem::invisible_b (here))
1128 // ugh, see chord-tremolo.ly
1129 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1132 b.translate (Offset (tx, -dir * bdy * j));
1133 rightbeams.add_molecule (b);
1138 a = Lookup::beam (dydx, w, thick);
1140 for (; j < rwholebeams + rhalfs; j++)
1143 b.translate_axis (- dir * bdy * j, Y_AXIS);
1144 rightbeams.add_molecule (b);
1148 leftbeams.add_molecule (rightbeams);
1150 /* Does beam quanting think of the asymetry of beams?
1151 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1156 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1158 Beam::brew_molecule (SCM smob)
1160 Grob *me =unsmob_grob (smob);
1163 if (!gh_pair_p (me->get_grob_property ("stems")))
1166 Link_array<Item>stems =
1167 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1168 if (visible_stem_count (me))
1170 // ugh -> use commonx
1171 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1172 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1176 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1177 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1180 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1181 Real dy = pos.delta ();
1182 Real dydx = dy && dx ? dy/dx : 0;
1184 for (int i=0; i < stems.size (); i++)
1186 Item *item = stems[i];
1187 Item *prev = (i > 0)? stems[i-1] : 0;
1188 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1190 Molecule sb = stem_beams (me, item, next, prev, dydx);
1191 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1192 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1193 mol.add_molecule (sb);
1196 mol.translate_axis (x0
1197 - dynamic_cast<Spanner*> (me)
1198 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1204 This code prints the demerits for each beam. Perhaps this
1205 should be switchable for those who want to twiddle with the
1211 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1214 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1217 SCM properties = Font_interface::font_alist_chain (me);
1219 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1220 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1223 return mol.smobbed_copy ();
1227 Beam::forced_stem_count (Grob *me)
1229 Link_array<Item>stems =
1230 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1232 for (int i=0; i < stems.size (); i++)
1236 if (Stem::invisible_b (s))
1239 if (((int)Stem::chord_start_f (s))
1240 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1250 Beam::visible_stem_count (Grob *me)
1252 Link_array<Item>stems =
1253 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1255 for (int i = stems.size (); i--;)
1257 if (!Stem::invisible_b (stems[i]))
1264 Beam::first_visible_stem (Grob *me)
1266 Link_array<Item>stems =
1267 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1269 for (int i = 0; i < stems.size (); i++)
1271 if (!Stem::invisible_b (stems[i]))
1278 Beam::last_visible_stem (Grob *me)
1280 Link_array<Item>stems =
1281 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1282 for (int i = stems.size (); i--;)
1284 if (!Stem::invisible_b (stems[i]))
1294 handle rest under beam (do_post: beams are calculated now)
1295 what about combination of collisions and rest under beam.
1299 rest -> stem -> beam -> interpolate_y_position ()
1301 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1303 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1305 Grob *rest = unsmob_grob (element_smob);
1306 Axis a = (Axis) gh_scm2int (axis);
1308 assert (a == Y_AXIS);
1310 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1313 return gh_double2scm (0.0);
1314 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1316 || !Beam::has_interface (beam)
1317 || !Beam::visible_stem_count (beam))
1318 return gh_double2scm (0.0);
1320 // make callback for rest from this.
1321 // todo: make sure this calced already.
1323 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1324 Interval pos (0, 0);
1325 SCM s = beam->get_grob_property ("positions");
1326 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1327 pos = ly_scm2interval (s);
1329 Real dy = pos.delta ();
1330 // ugh -> use commonx
1331 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1332 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1333 Real dydx = dy && dx ? dy/dx : 0;
1335 Direction d = Stem::get_direction (stem);
1336 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1338 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1341 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1344 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1346 minimum_dist + -d * (beamy - rest_dim) >? 0;
1348 int stafflines = Staff_symbol_referencer::line_count (rest);
1350 // move discretely by half spaces.
1351 int discrete_dist = int (ceil (dist));
1353 // move by whole spaces inside the staff.
1354 if (discrete_dist < stafflines+1)
1355 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1357 return gh_double2scm (-d * discrete_dist);
1363 ADD_INTERFACE (Beam, "beam-interface",
1366 #'thickness= weight of beams, in staffspace
1369 We take the least squares line through the ideal-length stems, and
1370 then damp that using
1372 damped = tanh (slope)
1374 this gives an unquantized left and right position for the beam end.
1375 Then we take all combinations of quantings near these left and right
1376 positions, and give them a score (according to how close they are to
1377 the ideal slope, how close the result is to the ideal stems, etc.). We
1378 take the best scoring combination.
1381 "position-callbacks 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");