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 return SCM_UNSPECIFIED;
370 - Make all demerits customisable
372 - One sensible check per demerit (what's this --hwn)
374 - Add demerits for quants per se, as to forbid a specific quant
378 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
380 Beam::quanting (SCM smob)
382 Grob *me = unsmob_grob (smob);
384 SCM s = me->get_grob_property ("positions");
385 Real yl = gh_scm2double (gh_car (s));
386 Real yr = gh_scm2double (gh_cdr (s));
388 Real ss = Staff_symbol_referencer::staff_space (me);
389 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
390 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
393 SCM sdy = me->get_grob_property ("least-squares-dy");
394 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
397 Real sit = (thickness - slt) / 2;
399 Real hang = 1.0 - (thickness - slt) / 2;
400 Real quants [] = {straddle, sit, inter, hang };
402 int num_quants = int (sizeof (quants)/sizeof (Real));
407 going to REGION_SIZE == 2, yields another 0.6 second with
411 (result indexes between 70 and 575) ? --hwn.
415 const int REGION_SIZE = 3;
416 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
417 for (int j = 0; j < num_quants; j++)
419 quantsl.push (i + quants[j] + int (yl));
420 quantsr.push (i + quants[j] + int (yr));
423 Array<Quant_score> qscores;
425 for (int l =0; l < quantsl.size (); l++)
426 for (int r =0; r < quantsr.size (); r++)
438 This is a longish function, but we don't separate this out into
439 neat modular separate subfunctions, as the subfunctions would be
440 called for many values of YL, YR. By precomputing various
441 parameters outside of the loop, we can save a lot of time.
444 for (int i = qscores.size (); i--;)
445 if (qscores[i].demerits < 100)
448 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
452 Real rad = Staff_symbol_referencer::staff_radius (me);
453 int multiplicity = get_multiplicity (me);
454 Real interbeam = multiplicity < 4
455 ? (2*ss + slt - thickness) / 2.0
456 : (3*ss + slt - thickness) / 3.0;
458 for (int i = qscores.size (); i--;)
459 if (qscores[i].demerits < 100)
462 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
463 rad, slt, thickness, interbeam,
469 Do stem lengths. These depend on YL and YR linearly, so we can
470 precompute for every stem 2 factors.
472 Link_array<Grob> stems=
473 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
474 Array<Stem_info> stem_infos;
475 Array<Real> lbase_lengths;
476 Array<Real> rbase_lengths;
478 Array<int> directions;
479 for (int i= 0; i < stems.size(); i++)
482 stem_infos.push( Stem::calc_stem_info (s));
484 Real b = calc_stem_y (me, s, Interval (1,0));
485 lbase_lengths.push (b);
487 b = calc_stem_y (me, s, Interval (0,1));
488 rbase_lengths.push (b);
489 directions.push( Directional_element_interface::get( s));
492 for (int i = qscores.size (); i--;)
493 if (qscores[i].demerits < 100)
496 += score_stem_lengths (stems, stem_infos,
497 lbase_lengths, rbase_lengths,
499 me, qscores[i].yl, qscores[i].yr);
505 for (int i = qscores.size (); i--;)
507 if (qscores[i].demerits < best)
509 best = qscores [i].demerits ;
515 me->set_grob_property ("positions",
516 gh_cons (gh_double2scm (qscores[best_idx].yl),
517 gh_double2scm (qscores[best_idx].yr))
523 me->set_grob_property ("quant-score",
524 gh_double2scm (qscores[best_idx].demerits));
525 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
528 return SCM_UNSPECIFIED;
532 Beam::score_stem_lengths (Link_array<Grob>stems,
533 Array<Stem_info> stem_infos,
534 Array<Real> left_factor,
535 Array<Real> right_factor,
536 Array<int> directions,
537 Grob*me, Real yl, Real yr)
539 Real demerit_score = 0.0 ;
541 for (int i=0; i < stems.size (); i++)
544 if (Stem::invisible_b (s))
548 yl * left_factor[i] + right_factor[i]* yr;
550 Stem_info info = stem_infos[i];
551 Direction d = Direction (directions[i]);
553 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
554 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
556 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
559 demerit_score *= 2.0 /stems.size ();
561 return demerit_score;
565 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
566 Real dy_mus, Real dy_damp)
571 if (sign (dy_damp) != sign (dy))
576 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
579 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
586 return x - floor (x);
590 Beam::score_forbidden_quants (Grob*me,
594 Real thickness, Real interbeam,
600 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
602 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
605 // todo: use multiplicity of outer stems.
606 if (multiplicity >= 2)
610 Real sit = (thickness - slt) / 2;
612 Real hang = 1.0 - (thickness - slt) / 2;
614 Direction dir = Directional_element_interface::get (me);
615 if (fabs (yl - dir * interbeam) < rad
616 && fabs (my_modf (yl) - inter) < 1e-3)
618 if (fabs (yr - dir * interbeam) < rad
619 && fabs (my_modf (yr) - inter) < 1e-3)
625 Can't we simply compute the distance between the nearest
626 staffline and the secondary beam? That would get rid of the
627 silly case analysis here (which is probably not when we have
628 different beam-thicknesses.)
633 // hmm, without Interval/Drul_array, you get ~ 4x same code...
634 if (fabs (yl - dir * interbeam) < rad + inter)
636 if (dir == UP && dy <= eps
637 && fabs (my_modf (yl) - sit) < eps)
640 if (dir == DOWN && dy >= eps
641 && fabs (my_modf (yl) - hang) < eps)
645 if (fabs (yr - dir * interbeam) < rad + inter)
647 if (dir == UP && dy >= eps
648 && fabs (my_modf (yr) - sit) < eps)
651 if (dir == DOWN && dy <= eps
652 && fabs (my_modf (yr) - hang) < eps)
656 if (multiplicity >= 3)
658 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
660 if (dir == UP && dy <= eps
661 && fabs (my_modf (yl) - straddle) < eps)
664 if (dir == DOWN && dy >= eps
665 && fabs (my_modf (yl) - straddle) < eps)
669 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
671 if (dir == UP && dy >= eps
672 && fabs (my_modf (yr) - straddle) < eps)
675 if (dir == DOWN && dy <= eps
676 && fabs (my_modf (yr) - straddle) < eps)
687 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
689 Beam::least_squares (SCM smob)
691 Grob *me = unsmob_grob (smob);
693 int count = visible_stem_count (me);
698 me->set_grob_property ("positions", ly_interval2scm (pos));
699 return SCM_UNSPECIFIED;
702 Direction dir = Directional_element_interface::get (me);
704 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
705 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
709 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
710 Stem::chord_start_f (last_visible_stem (me)));
714 TODO : use scoring for this.
716 complicated, because we take stem-info.ideal for determining
720 /* Make simple beam on middle line have small tilt */
721 if (!ideal[LEFT] && chord.delta () && count == 2)
723 Direction d = (Direction) (sign (chord.delta ()) * dir);
724 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
737 Array<Offset> ideals;
739 // ugh -> use commonx
740 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
741 Link_array<Item> stems=
742 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
744 for (int i=0; i < stems.size (); i++)
747 if (Stem::invisible_b (s))
749 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
750 Stem::calc_stem_info (s).ideal_y));
754 minimise_least_squares (&dydx, &y, ideals);
756 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
758 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
760 pos = Interval (y*dir, (y+dy) * dir);
763 me->set_grob_property ("positions", ly_interval2scm (pos));
764 return SCM_UNSPECIFIED;
767 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
769 Beam::check_concave (SCM smob)
771 Grob *me = unsmob_grob (smob);
773 Link_array<Item> stems =
774 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
776 for (int i = 0; i < stems.size ();)
778 if (Stem::invisible_b (stems[i]))
784 if (stems.size () < 3)
785 return SCM_UNSPECIFIED;
787 Direction dir = Directional_element_interface::get (me);
788 /* Concaveness #1: If distance of an inner notehead to line between
789 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
790 beam is concave (Heinz Stolba). */
791 bool concaveness1 = false;
792 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
795 Real dy = Stem::chord_start_f (stems.top ())
796 - Stem::chord_start_f (stems[0]);
797 Real slope = dy / (stems.size () - 1);
799 Real y0 = Stem::chord_start_f (stems[0]);
800 for (int i = 1; i < stems.size () - 1; i++)
802 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
812 /* Concaveness #2: Sum distances of inner noteheads that fall
813 outside the interval of the two outer noteheads */
814 Real concaveness2 = 0;
815 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
816 if (!concaveness1 && r2 > 0)
819 Interval iv (Stem::chord_start_f (stems[0]),
820 Stem::chord_start_f (stems.top ()));
822 if (iv[MAX] < iv[MIN])
825 for (int i = 1; i < stems.size () - 1; i++)
828 Real f = Stem::chord_start_f (stems[i]);
829 if ((c = f - iv[MAX]) > 0)
831 else if ((c = f - iv[MIN]) < 0)
837 concaveness2 = concave / (stems.size () - 2);
838 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
839 to behave as baerenreiter. */
840 concaveness2 /= (stems.size () - 2);
843 /* TODO: some sort of damping iso -> plain horizontal */
844 if (concaveness1 || concaveness2 > r2)
846 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
847 Real r = pos.linear_combination (0);
848 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
849 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
852 return SCM_UNSPECIFIED;
855 /* This neat trick is by Werner Lemberg,
856 damped = tanh (slope)
857 corresponds with some tables in [Wanske] CHECKME */
858 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
860 Beam::slope_damping (SCM smob)
862 Grob *me = unsmob_grob (smob);
864 if (visible_stem_count (me) <= 1)
865 return SCM_UNSPECIFIED;
867 SCM s = me->get_grob_property ("damping");
868 int damping = gh_scm2int (s);
872 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
873 Real dy = pos.delta ();
875 // ugh -> use commonx
876 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
877 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
878 Real dydx = dy && dx ? dy/dx : 0;
879 dydx = 0.6 * tanh (dydx) / damping;
881 Real damped_dy = dydx * dx;
882 pos[LEFT] += (dy - damped_dy) / 2;
883 pos[RIGHT] -= (dy - damped_dy) / 2;
885 me->set_grob_property ("positions", ly_interval2scm (pos));
887 return SCM_UNSPECIFIED;
890 MAKE_SCHEME_CALLBACK (Beam, end_after_line_breaking, 1);
892 Beam::end_after_line_breaking (SCM smob)
894 Grob *me = unsmob_grob (smob);
895 set_stem_lengths (me);
897 return SCM_UNSPECIFIED;
901 Calculate the Y position of the stem-end, given the Y-left, Y-right
902 in POS, and for stem S.
905 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
907 int beam_multiplicity = get_multiplicity (me);
908 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
910 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
911 Real interbeam = get_interbeam (me);
913 // ugh -> use commonx
914 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
915 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
916 Real dy = pos.delta ();
917 Real stem_y = (dy && dx
918 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
923 Direction dir = Directional_element_interface::get (me);
924 Direction sdir = Directional_element_interface::get (s);
929 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
931 // huh, why not for first visible?
933 Grob *last_visible = last_visible_stem (me);
936 if ( Staff_symbol_referencer::staff_symbol_l (s)
937 != Staff_symbol_referencer::staff_symbol_l (last_visible))
938 stem_y += Directional_element_interface::get (me)
939 * (beam_multiplicity - stem_multiplicity) * interbeam;
942 programming_error ("No last visible stem");
949 Hmm. At this time, beam position and slope are determined. Maybe,
950 stem directions and length should set to relative to the chord's
951 position of the beam. */
953 Beam::set_stem_lengths (Grob *me)
955 Link_array<Item> stems=
956 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
958 if (stems.size () <= 1)
961 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
962 for (int i=1; i < stems.size (); i++)
963 if (!Stem::invisible_b (stems[i]))
964 common = common->common_refpoint (stems[i], Y_AXIS);
966 Direction dir = Directional_element_interface::get (me);
967 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
968 Real staff_space = Staff_symbol_referencer::staff_space (me);
969 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
970 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
971 for (int i=0; i < stems.size (); i++)
974 if (Stem::invisible_b (s))
977 Real stem_y = calc_stem_y (me, s, pos);
979 // doesn't play well with dvips
981 if (Stem::get_direction (s) == dir)
982 stem_y += Stem::get_direction (s) * thick / 2;
984 /* caution: stem measures in staff-positions */
985 Real id = me->relative_coordinate (common, Y_AXIS)
986 - stems[i]->relative_coordinate (common, Y_AXIS);
987 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
992 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
994 Link_array<Grob> stems=
995 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
998 for (int i=0; i < stems.size (); i++)
1002 /* Don't overwrite user override (?) */
1003 if (Stem::beam_count (stems[i], d) == -1
1004 /* Don't set beaming for outside of outer stems */
1005 && ! (d == LEFT && i == 0)
1006 && ! (d == RIGHT && i == stems.size () -1))
1008 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1009 Stem::set_beaming (stems[i], b, d);
1012 while (flip (&d) != LEFT);
1019 beams to go with one stem.
1024 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1026 // ugh -> use commonx
1028 && ! (next->relative_coordinate (0, X_AXIS)
1029 > here->relative_coordinate (0, X_AXIS)))
1031 && ! (prev->relative_coordinate (0, X_AXIS)
1032 < here->relative_coordinate (0, X_AXIS))))
1033 programming_error ("Beams are not left-to-right");
1035 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1036 Real bdy = get_interbeam (me);
1039 Molecule rightbeams;
1042 if (!Stem::first_head (here))
1045 int t = Stem::type_i (here);
1047 SCM proc = me->get_grob_property ("flag-width-function");
1048 SCM result = gh_call1 (proc, gh_int2scm (t));
1049 nw_f = gh_scm2double (result);
1053 Direction dir = Directional_element_interface::get (me);
1055 /* [Tremolo] beams on whole notes may not have direction set? */
1057 dir = Directional_element_interface::get (here);
1060 /* half beams extending to the left. */
1063 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1064 - Stem::beam_count (prev, RIGHT);
1065 int lwholebeams= Stem::beam_count (here, LEFT)
1066 <? Stem::beam_count (prev, RIGHT);
1068 /* Half beam should be one note-width,
1069 but let's make sure two half-beams never touch */
1071 // FIXME: TODO (check) stem width / sloped beams
1072 Real w = here->relative_coordinate (0, X_AXIS)
1073 - prev->relative_coordinate (0, X_AXIS);
1074 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1076 * me->paper_l ()->get_var ("linethickness");
1080 if (lhalfs) // generates warnings if not
1081 a = Lookup::beam (dydx, w + stem_w, thick);
1082 a.translate (Offset (-w, -w * dydx));
1083 a.translate_axis (-stem_w/2, X_AXIS);
1084 for (int j = 0; j < lhalfs; j++)
1087 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1088 leftbeams.add_molecule (b);
1094 int rhalfs = Stem::beam_count (here, RIGHT)
1095 - Stem::beam_count (next, LEFT);
1096 int rwholebeams= Stem::beam_count (here, RIGHT)
1097 <? Stem::beam_count (next, LEFT);
1099 Real w = next->relative_coordinate (0, X_AXIS)
1100 - here->relative_coordinate (0, X_AXIS);
1102 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1104 * me->paper_l ()->get_var ("linethickness");
1106 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1107 a.translate_axis (- stem_w/2, X_AXIS);
1111 SCM gap = me->get_grob_property ("gap");
1112 if (gh_number_p (gap))
1114 int gap_i = gh_scm2int ((gap));
1115 int nogap = rwholebeams - gap_i;
1117 for (; j < nogap; j++)
1120 b.translate_axis (-dir * bdy * j, Y_AXIS);
1121 rightbeams.add_molecule (b);
1123 if (Stem::invisible_b (here))
1128 a = Lookup::beam (dydx, w + stem_w, thick);
1131 for (; j < rwholebeams; j++)
1135 if (Stem::invisible_b (here))
1136 // ugh, see chord-tremolo.ly
1137 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1140 b.translate (Offset (tx, -dir * bdy * j));
1141 rightbeams.add_molecule (b);
1146 a = Lookup::beam (dydx, w, thick);
1148 for (; j < rwholebeams + rhalfs; j++)
1151 b.translate_axis (- dir * bdy * j, Y_AXIS);
1152 rightbeams.add_molecule (b);
1156 leftbeams.add_molecule (rightbeams);
1158 /* Does beam quanting think of the asymetry of beams?
1159 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1164 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1166 Beam::brew_molecule (SCM smob)
1168 Grob *me =unsmob_grob (smob);
1171 if (!gh_pair_p (me->get_grob_property ("stems")))
1174 Link_array<Item>stems =
1175 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1176 if (visible_stem_count (me))
1178 // ugh -> use commonx
1179 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1180 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1184 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1185 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1188 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1189 Real dy = pos.delta ();
1190 Real dydx = dy && dx ? dy/dx : 0;
1192 for (int i=0; i < stems.size (); i++)
1194 Item *item = stems[i];
1195 Item *prev = (i > 0)? stems[i-1] : 0;
1196 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1198 Molecule sb = stem_beams (me, item, next, prev, dydx);
1199 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1200 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1201 mol.add_molecule (sb);
1204 mol.translate_axis (x0
1205 - dynamic_cast<Spanner*> (me)
1206 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1212 This code prints the demerits for each beam. Perhaps this
1213 should be switchable for those who want to twiddle with the
1219 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1222 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1225 SCM properties = Font_interface::font_alist_chain (me);
1227 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1228 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1231 return mol.smobbed_copy ();
1235 Beam::forced_stem_count (Grob *me)
1237 Link_array<Item>stems =
1238 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1240 for (int i=0; i < stems.size (); i++)
1244 if (Stem::invisible_b (s))
1247 if (((int)Stem::chord_start_f (s))
1248 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1258 Beam::visible_stem_count (Grob *me)
1260 Link_array<Item>stems =
1261 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1263 for (int i = stems.size (); i--;)
1265 if (!Stem::invisible_b (stems[i]))
1272 Beam::first_visible_stem (Grob *me)
1274 Link_array<Item>stems =
1275 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1277 for (int i = 0; i < stems.size (); i++)
1279 if (!Stem::invisible_b (stems[i]))
1286 Beam::last_visible_stem (Grob *me)
1288 Link_array<Item>stems =
1289 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1290 for (int i = stems.size (); i--;)
1292 if (!Stem::invisible_b (stems[i]))
1302 handle rest under beam (do_post: beams are calculated now)
1303 what about combination of collisions and rest under beam.
1307 rest -> stem -> beam -> interpolate_y_position ()
1309 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1311 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1313 Grob *rest = unsmob_grob (element_smob);
1314 Axis a = (Axis) gh_scm2int (axis);
1316 assert (a == Y_AXIS);
1318 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1321 return gh_double2scm (0.0);
1322 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1324 || !Beam::has_interface (beam)
1325 || !Beam::visible_stem_count (beam))
1326 return gh_double2scm (0.0);
1328 // make callback for rest from this.
1329 // todo: make sure this calced already.
1331 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1332 Interval pos (0, 0);
1333 SCM s = beam->get_grob_property ("positions");
1334 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1335 pos = ly_scm2interval (s);
1337 Real dy = pos.delta ();
1338 // ugh -> use commonx
1339 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1340 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1341 Real dydx = dy && dx ? dy/dx : 0;
1343 Direction d = Stem::get_direction (stem);
1344 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1346 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1349 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1352 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1354 minimum_dist + -d * (beamy - rest_dim) >? 0;
1356 int stafflines = Staff_symbol_referencer::line_count (rest);
1358 // move discretely by half spaces.
1359 int discrete_dist = int (ceil (dist));
1361 // move by whole spaces inside the staff.
1362 if (discrete_dist < stafflines+1)
1363 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1365 return gh_double2scm (-d * discrete_dist);
1370 Beam::has_interface (Grob *me)
1372 return me->has_interface (ly_symbol2scm ("beam-interface"));
1376 ADD_INTERFACE (Beam, "beam-interface",
1379 #'thickness= weight of beams, in staffspace
1382 We take the least squares line through the ideal-length stems, and
1383 then damp that using
1385 damped = tanh (slope)
1387 this gives an unquantized left and right position for the beam end.
1388 Then we take all combinations of quantings near these left and right
1389 positions, and give them a score (according to how close they are to
1390 the ideal slope, how close the result is to the ideal stems, etc.). We
1391 take the best scoring combination.
1394 "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");