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);
183 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
184 int current = sd ? (1 + d * sd)/2 : center_distance;
191 } while (flip (&d) != DOWN);
193 SCM func = me->get_grob_property ("dir-function");
194 SCM s = gh_call2 (func,
195 gh_cons (gh_int2scm (count[UP]),
196 gh_int2scm (count[DOWN])),
197 gh_cons (gh_int2scm (total[UP]),
198 gh_int2scm (total[DOWN])));
200 if (gh_number_p (s) && gh_scm2int (s))
203 /* If dir is not determined: get default */
204 return to_dir (me->get_grob_property ("neutral-direction"));
208 /* Set all stems with non-forced direction to beam direction.
209 Urg: non-forced should become `without/with unforced' direction,
210 once stem gets cleaned-up. */
212 Beam::set_stem_directions (Grob *me)
214 Link_array<Item> stems
215 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
216 Direction d = Directional_element_interface::get (me);
218 for (int i=0; i <stems.size (); i++)
221 SCM force = s->remove_grob_property ("dir-forced");
222 if (!gh_boolean_p (force) || !gh_scm2bool (force))
223 Directional_element_interface::set (s, d);
227 /* Simplistic auto-knees; only consider vertical gap between two
230 `Forced' stem directions are ignored. If you don't want auto-knees,
231 don't set, or unset auto-knee-gap. */
233 Beam::consider_auto_knees (Grob *me)
235 SCM scm = me->get_grob_property ("auto-knee-gap");
237 if (gh_number_p (scm))
241 Real staff_space = Staff_symbol_referencer::staff_space (me);
242 Real gap = gh_scm2double (scm) / staff_space;
244 Direction d = Directional_element_interface::get (me);
245 Link_array<Item> stems=
246 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
248 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
249 for (int i=1; i < stems.size (); i++)
250 if (!Stem::invisible_b (stems[i]))
251 common = common->common_refpoint (stems[i], Y_AXIS);
254 for (int i=1; i < stems.size (); i++)
256 if (!Stem::invisible_b (stems[i-1]))
258 if (Stem::invisible_b (stems[l]))
260 if (Stem::invisible_b (stems[i]))
263 Real left = Stem::extremal_heads (stems[l])[d]
264 ->relative_coordinate (common, Y_AXIS);
265 Real right = Stem::extremal_heads (stems[i])[-d]
266 ->relative_coordinate (common, Y_AXIS);
268 Real dy = right - left;
272 knee_y = (right + left) / 2;
280 for (int i=0; i < stems.size (); i++)
282 if (Stem::invisible_b (stems[i]))
285 Real y = Stem::extremal_heads (stems[i])[d]
286 ->relative_coordinate (common, Y_AXIS);
288 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
289 s->set_grob_property ("dir-forced", SCM_BOOL_T);
295 /* Set stem's shorten property if unset.
298 take some y-position (chord/beam/nearest?) into account
299 scmify forced-fraction
303 why is shorten stored in beam, and not directly in stem?
307 Beam::set_stem_shorten (Grob *m)
309 Spanner*me = dynamic_cast<Spanner*> (m);
311 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
313 int multiplicity = get_multiplicity (me);
315 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
316 if (shorten == SCM_EOL)
319 int sz = scm_ilength (shorten);
321 Real staff_space = Staff_symbol_referencer::staff_space (me);
322 SCM shorten_elt = scm_list_ref (shorten,
323 gh_int2scm (multiplicity <? (sz - 1)));
324 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
326 /* your similar cute comment here */
327 shorten_f *= forced_fraction;
329 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
332 /* Call list of y-dy-callbacks, that handle setting of
333 grob-properties y, dy.
335 User may set grob-properties: y-position-hs and height-hs
336 (to be fixed) that override the calculated y and dy.
338 Because y and dy cannot be calculated and quanted separately, we
339 always calculate both, then check for user override. */
340 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
342 Beam::after_line_breaking (SCM smob)
344 Grob *me = unsmob_grob (smob);
346 /* Copy to mutable list. */
347 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
348 me->set_grob_property ("positions", s);
350 if (ly_car (s) != SCM_BOOL_F)
351 return SCM_UNSPECIFIED;
353 // one wonders if such genericity is necessary --hwn.
354 SCM callbacks = me->get_grob_property ("position-callbacks");
355 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
356 gh_call1 (ly_car (i), smob);
358 set_stem_lengths (me);
359 return SCM_UNSPECIFIED;
373 - Make all demerits customisable
375 - One sensible check per demerit (what's this --hwn)
377 - Add demerits for quants per se, as to forbid a specific quant
381 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
383 Beam::quanting (SCM smob)
385 Grob *me = unsmob_grob (smob);
387 SCM s = me->get_grob_property ("positions");
388 Real yl = gh_scm2double (gh_car (s));
389 Real yr = gh_scm2double (gh_cdr (s));
391 Real ss = Staff_symbol_referencer::staff_space (me);
392 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
393 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
396 SCM sdy = me->get_grob_property ("least-squares-dy");
397 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
400 Real sit = (thickness - slt) / 2;
402 Real hang = 1.0 - (thickness - slt) / 2;
403 Real quants [] = {straddle, sit, inter, hang };
405 int num_quants = int (sizeof (quants)/sizeof (Real));
410 going to REGION_SIZE == 2, yields another 0.6 second with
414 (result indexes between 70 and 575) ? --hwn.
418 const int REGION_SIZE = 3;
419 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
420 for (int j = 0; j < num_quants; j++)
422 quantsl.push (i + quants[j] + int (yl));
423 quantsr.push (i + quants[j] + int (yr));
426 Array<Quant_score> qscores;
428 for (int l =0; l < quantsl.size (); l++)
429 for (int r =0; r < quantsr.size (); r++)
441 This is a longish function, but we don't separate this out into
442 neat modular separate subfunctions, as the subfunctions would be
443 called for many values of YL, YR. By precomputing various
444 parameters outside of the loop, we can save a lot of time.
447 for (int i = qscores.size (); i--;)
448 if (qscores[i].demerits < 100)
451 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
455 Real rad = Staff_symbol_referencer::staff_radius (me);
456 int multiplicity = get_multiplicity (me);
457 Real interbeam = multiplicity < 4
458 ? (2*ss + slt - thickness) / 2.0
459 : (3*ss + slt - thickness) / 3.0;
461 for (int i = qscores.size (); i--;)
462 if (qscores[i].demerits < 100)
465 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
466 rad, slt, thickness, interbeam,
472 Do stem lengths. These depend on YL and YR linearly, so we can
473 precompute for every stem 2 factors.
475 Link_array<Grob> stems=
476 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
477 Array<Stem_info> stem_infos;
478 Array<Real> lbase_lengths;
479 Array<Real> rbase_lengths;
481 Array<int> directions;
482 for (int i= 0; i < stems.size(); i++)
485 stem_infos.push( Stem::calc_stem_info (s));
487 Real b = calc_stem_y (me, s, Interval (1,0));
488 lbase_lengths.push (b);
490 b = calc_stem_y (me, s, Interval (0,1));
491 rbase_lengths.push (b);
492 directions.push( Directional_element_interface::get( s));
495 for (int i = qscores.size (); i--;)
496 if (qscores[i].demerits < 100)
499 += score_stem_lengths (stems, stem_infos,
500 lbase_lengths, rbase_lengths,
502 me, qscores[i].yl, qscores[i].yr);
508 for (int i = qscores.size (); i--;)
510 if (qscores[i].demerits < best)
512 best = qscores [i].demerits ;
518 me->set_grob_property ("positions",
519 gh_cons (gh_double2scm (qscores[best_idx].yl),
520 gh_double2scm (qscores[best_idx].yr))
526 me->set_grob_property ("quant-score",
527 gh_double2scm (qscores[best_idx].demerits));
528 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
531 return SCM_UNSPECIFIED;
535 Beam::score_stem_lengths (Link_array<Grob>stems,
536 Array<Stem_info> stem_infos,
537 Array<Real> left_factor,
538 Array<Real> right_factor,
539 Array<int> directions,
540 Grob*me, Real yl, Real yr)
542 Real demerit_score = 0.0 ;
544 for (int i=0; i < stems.size (); i++)
547 if (Stem::invisible_b (s))
551 yl * left_factor[i] + right_factor[i]* yr;
553 Stem_info info = stem_infos[i];
554 Direction d = Direction (directions[i]);
556 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
557 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
559 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
562 demerit_score *= 2.0 / stems.size ();
564 return demerit_score;
568 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
569 Real dy_mus, Real dy_damp)
574 if (sign (dy_damp) != sign (dy))
579 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
582 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
589 return x - floor (x);
593 Beam::score_forbidden_quants (Grob*me,
597 Real thickness, Real interbeam,
603 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
605 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
608 // todo: use multiplicity of outer stems.
609 if (multiplicity >= 2)
613 Real sit = (thickness - slt) / 2;
615 Real hang = 1.0 - (thickness - slt) / 2;
617 Direction dir = Directional_element_interface::get (me);
618 if (fabs (yl - dir * interbeam) < rad
619 && fabs (my_modf (yl) - inter) < 1e-3)
621 if (fabs (yr - dir * interbeam) < rad
622 && fabs (my_modf (yr) - inter) < 1e-3)
628 Can't we simply compute the distance between the nearest
629 staffline and the secondary beam? That would get rid of the
630 silly case analysis here (which is probably not when we have
631 different beam-thicknesses.)
636 // hmm, without Interval/Drul_array, you get ~ 4x same code...
637 if (fabs (yl - dir * interbeam) < rad + inter)
639 if (dir == UP && dy <= eps
640 && fabs (my_modf (yl) - sit) < eps)
643 if (dir == DOWN && dy >= eps
644 && fabs (my_modf (yl) - hang) < eps)
648 if (fabs (yr - dir * interbeam) < rad + inter)
650 if (dir == UP && dy >= eps
651 && fabs (my_modf (yr) - sit) < eps)
654 if (dir == DOWN && dy <= eps
655 && fabs (my_modf (yr) - hang) < eps)
659 if (multiplicity >= 3)
661 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
663 if (dir == UP && dy <= eps
664 && fabs (my_modf (yl) - straddle) < eps)
667 if (dir == DOWN && dy >= eps
668 && fabs (my_modf (yl) - straddle) < eps)
672 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
674 if (dir == UP && dy >= eps
675 && fabs (my_modf (yr) - straddle) < eps)
678 if (dir == DOWN && dy <= eps
679 && fabs (my_modf (yr) - straddle) < eps)
690 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
692 Beam::least_squares (SCM smob)
694 Grob *me = unsmob_grob (smob);
696 int count = visible_stem_count (me);
701 me->set_grob_property ("positions", ly_interval2scm (pos));
702 return SCM_UNSPECIFIED;
705 Direction dir = Directional_element_interface::get (me);
707 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
708 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
712 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
713 Stem::chord_start_f (last_visible_stem (me)));
717 TODO : use scoring for this.
719 complicated, because we take stem-info.ideal for determining
723 /* Make simple beam on middle line have small tilt */
724 if (!ideal[LEFT] && chord.delta () && count == 2)
726 Direction d = (Direction) (sign (chord.delta ()) * dir);
727 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
740 Array<Offset> ideals;
742 // ugh -> use commonx
743 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
744 Link_array<Item> stems=
745 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
747 for (int i=0; i < stems.size (); i++)
750 if (Stem::invisible_b (s))
752 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
753 Stem::calc_stem_info (s).ideal_y));
757 minimise_least_squares (&dydx, &y, ideals);
759 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
761 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
763 pos = Interval (y*dir, (y+dy) * dir);
766 me->set_grob_property ("positions", ly_interval2scm (pos));
767 return SCM_UNSPECIFIED;
770 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
772 Beam::check_concave (SCM smob)
774 Grob *me = unsmob_grob (smob);
776 Link_array<Item> stems =
777 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
779 for (int i = 0; i < stems.size ();)
781 if (Stem::invisible_b (stems[i]))
787 if (stems.size () < 3)
788 return SCM_UNSPECIFIED;
790 Direction dir = Directional_element_interface::get (me);
791 /* Concaveness #1: If distance of an inner notehead to line between
792 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
793 beam is concave (Heinz Stolba). */
794 bool concaveness1 = false;
795 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
798 Real dy = Stem::chord_start_f (stems.top ())
799 - Stem::chord_start_f (stems[0]);
800 Real slope = dy / (stems.size () - 1);
802 Real y0 = Stem::chord_start_f (stems[0]);
803 for (int i = 1; i < stems.size () - 1; i++)
805 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
815 /* Concaveness #2: Sum distances of inner noteheads that fall
816 outside the interval of the two outer noteheads */
817 Real concaveness2 = 0;
818 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
819 if (!concaveness1 && r2 > 0)
822 Interval iv (Stem::chord_start_f (stems[0]),
823 Stem::chord_start_f (stems.top ()));
825 if (iv[MAX] < iv[MIN])
828 for (int i = 1; i < stems.size () - 1; i++)
831 Real f = Stem::chord_start_f (stems[i]);
832 if ((c = f - iv[MAX]) > 0)
834 else if ((c = f - iv[MIN]) < 0)
840 concaveness2 = concave / (stems.size () - 2);
841 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
842 to behave as baerenreiter. */
843 concaveness2 /= (stems.size () - 2);
846 /* TODO: some sort of damping iso -> plain horizontal */
847 if (concaveness1 || concaveness2 > r2)
849 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
850 Real r = pos.linear_combination (0);
851 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
852 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
855 return SCM_UNSPECIFIED;
858 /* This neat trick is by Werner Lemberg,
859 damped = tanh (slope)
860 corresponds with some tables in [Wanske] CHECKME */
861 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
863 Beam::slope_damping (SCM smob)
865 Grob *me = unsmob_grob (smob);
867 if (visible_stem_count (me) <= 1)
868 return SCM_UNSPECIFIED;
870 SCM s = me->get_grob_property ("damping");
871 int damping = gh_scm2int (s);
875 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
876 Real dy = pos.delta ();
878 // ugh -> use commonx
879 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
880 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
881 Real dydx = dy && dx ? dy/dx : 0;
882 dydx = 0.6 * tanh (dydx) / damping;
884 Real damped_dy = dydx * dx;
885 pos[LEFT] += (dy - damped_dy) / 2;
886 pos[RIGHT] -= (dy - damped_dy) / 2;
888 me->set_grob_property ("positions", ly_interval2scm (pos));
890 return SCM_UNSPECIFIED;
894 Calculate the Y position of the stem-end, given the Y-left, Y-right
895 in POS, and for stem S.
898 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
900 int beam_multiplicity = get_multiplicity (me);
901 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
903 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
904 Real interbeam = get_interbeam (me);
906 // ugh -> use commonx
907 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
908 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
909 Real dy = pos.delta ();
910 Real stem_y = (dy && dx
911 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
916 Direction dir = Directional_element_interface::get (me);
917 Direction sdir = Directional_element_interface::get (s);
922 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
924 // huh, why not for first visible?
926 Grob *last_visible = last_visible_stem (me);
929 if ( Staff_symbol_referencer::staff_symbol_l (s)
930 != Staff_symbol_referencer::staff_symbol_l (last_visible))
931 stem_y += Directional_element_interface::get (me)
932 * (beam_multiplicity - stem_multiplicity) * interbeam;
935 programming_error ("No last visible stem");
942 Hmm. At this time, beam position and slope are determined. Maybe,
943 stem directions and length should set to relative to the chord's
944 position of the beam. */
946 Beam::set_stem_lengths (Grob *me)
948 Link_array<Item> stems=
949 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
951 if (stems.size () <= 1)
954 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
955 for (int i=1; i < stems.size (); i++)
956 if (!Stem::invisible_b (stems[i]))
957 common = common->common_refpoint (stems[i], Y_AXIS);
959 Direction dir = Directional_element_interface::get (me);
960 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
961 Real staff_space = Staff_symbol_referencer::staff_space (me);
962 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
963 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
964 for (int i=0; i < stems.size (); i++)
967 if (Stem::invisible_b (s))
970 Real stem_y = calc_stem_y (me, s, pos);
972 // doesn't play well with dvips
974 if (Stem::get_direction (s) == dir)
975 stem_y += Stem::get_direction (s) * thick / 2;
977 /* caution: stem measures in staff-positions */
978 Real id = me->relative_coordinate (common, Y_AXIS)
979 - stems[i]->relative_coordinate (common, Y_AXIS);
980 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
985 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
987 Link_array<Grob> stems=
988 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
991 for (int i=0; i < stems.size (); i++)
995 /* Don't overwrite user override (?) */
996 if (Stem::beam_count (stems[i], d) == -1
997 /* Don't set beaming for outside of outer stems */
998 && ! (d == LEFT && i == 0)
999 && ! (d == RIGHT && i == stems.size () -1))
1001 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1002 Stem::set_beaming (stems[i], b, d);
1005 while (flip (&d) != LEFT);
1012 beams to go with one stem.
1017 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1019 // ugh -> use commonx
1021 && ! (next->relative_coordinate (0, X_AXIS)
1022 > here->relative_coordinate (0, X_AXIS)))
1024 && ! (prev->relative_coordinate (0, X_AXIS)
1025 < here->relative_coordinate (0, X_AXIS))))
1026 programming_error ("Beams are not left-to-right");
1028 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1029 Real bdy = get_interbeam (me);
1032 Molecule rightbeams;
1035 if (!Stem::first_head (here))
1038 int t = Stem::type_i (here);
1040 SCM proc = me->get_grob_property ("flag-width-function");
1041 SCM result = gh_call1 (proc, gh_int2scm (t));
1042 nw_f = gh_scm2double (result);
1046 Direction dir = Directional_element_interface::get (me);
1048 /* [Tremolo] beams on whole notes may not have direction set? */
1050 dir = Directional_element_interface::get (here);
1053 /* half beams extending to the left. */
1056 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1057 - Stem::beam_count (prev, RIGHT);
1058 int lwholebeams= Stem::beam_count (here, LEFT)
1059 <? Stem::beam_count (prev, RIGHT);
1061 /* Half beam should be one note-width,
1062 but let's make sure two half-beams never touch */
1064 // FIXME: TODO (check) stem width / sloped beams
1065 Real w = here->relative_coordinate (0, X_AXIS)
1066 - prev->relative_coordinate (0, X_AXIS);
1067 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1069 * me->paper_l ()->get_var ("linethickness");
1073 if (lhalfs) // generates warnings if not
1074 a = Lookup::beam (dydx, w + stem_w, thick);
1075 a.translate (Offset (-w, -w * dydx));
1076 a.translate_axis (-stem_w/2, X_AXIS);
1077 for (int j = 0; j < lhalfs; j++)
1080 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1081 leftbeams.add_molecule (b);
1087 int rhalfs = Stem::beam_count (here, RIGHT)
1088 - Stem::beam_count (next, LEFT);
1089 int rwholebeams= Stem::beam_count (here, RIGHT)
1090 <? Stem::beam_count (next, LEFT);
1092 Real w = next->relative_coordinate (0, X_AXIS)
1093 - here->relative_coordinate (0, X_AXIS);
1095 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1097 * me->paper_l ()->get_var ("linethickness");
1099 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1100 a.translate_axis (- stem_w/2, X_AXIS);
1104 SCM gap = me->get_grob_property ("gap");
1105 if (gh_number_p (gap))
1107 int gap_i = gh_scm2int ((gap));
1108 int nogap = rwholebeams - gap_i;
1110 for (; j < nogap; j++)
1113 b.translate_axis (-dir * bdy * j, Y_AXIS);
1114 rightbeams.add_molecule (b);
1116 if (Stem::invisible_b (here))
1121 a = Lookup::beam (dydx, w + stem_w, thick);
1124 for (; j < rwholebeams; j++)
1128 if (Stem::invisible_b (here))
1129 // ugh, see chord-tremolo.ly
1130 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1133 b.translate (Offset (tx, -dir * bdy * j));
1134 rightbeams.add_molecule (b);
1139 a = Lookup::beam (dydx, w, thick);
1141 for (; j < rwholebeams + rhalfs; j++)
1144 b.translate_axis (- dir * bdy * j, Y_AXIS);
1145 rightbeams.add_molecule (b);
1149 leftbeams.add_molecule (rightbeams);
1151 /* Does beam quanting think of the asymetry of beams?
1152 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1157 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1159 Beam::brew_molecule (SCM smob)
1161 Grob *me =unsmob_grob (smob);
1164 if (!gh_pair_p (me->get_grob_property ("stems")))
1167 Link_array<Item>stems =
1168 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1169 if (visible_stem_count (me))
1171 // ugh -> use commonx
1172 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1173 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1177 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1178 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1181 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1182 Real dy = pos.delta ();
1183 Real dydx = dy && dx ? dy/dx : 0;
1185 for (int i=0; i < stems.size (); i++)
1187 Item *item = stems[i];
1188 Item *prev = (i > 0)? stems[i-1] : 0;
1189 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1191 Molecule sb = stem_beams (me, item, next, prev, dydx);
1192 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1193 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1194 mol.add_molecule (sb);
1197 mol.translate_axis (x0
1198 - dynamic_cast<Spanner*> (me)
1199 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1205 This code prints the demerits for each beam. Perhaps this
1206 should be switchable for those who want to twiddle with the
1212 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1215 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1218 SCM properties = Font_interface::font_alist_chain (me);
1220 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1221 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1224 return mol.smobbed_copy ();
1228 Beam::forced_stem_count (Grob *me)
1230 Link_array<Item>stems =
1231 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1233 for (int i=0; i < stems.size (); i++)
1237 if (Stem::invisible_b (s))
1240 if (((int)Stem::chord_start_f (s))
1241 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1251 Beam::visible_stem_count (Grob *me)
1253 Link_array<Item>stems =
1254 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1256 for (int i = stems.size (); i--;)
1258 if (!Stem::invisible_b (stems[i]))
1265 Beam::first_visible_stem (Grob *me)
1267 Link_array<Item>stems =
1268 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1270 for (int i = 0; i < stems.size (); i++)
1272 if (!Stem::invisible_b (stems[i]))
1279 Beam::last_visible_stem (Grob *me)
1281 Link_array<Item>stems =
1282 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1283 for (int i = stems.size (); i--;)
1285 if (!Stem::invisible_b (stems[i]))
1295 handle rest under beam (do_post: beams are calculated now)
1296 what about combination of collisions and rest under beam.
1300 rest -> stem -> beam -> interpolate_y_position ()
1302 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1304 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1306 Grob *rest = unsmob_grob (element_smob);
1307 Axis a = (Axis) gh_scm2int (axis);
1309 assert (a == Y_AXIS);
1311 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1314 return gh_double2scm (0.0);
1315 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1317 || !Beam::has_interface (beam)
1318 || !Beam::visible_stem_count (beam))
1319 return gh_double2scm (0.0);
1321 // make callback for rest from this.
1322 // todo: make sure this calced already.
1324 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1325 Interval pos (0, 0);
1326 SCM s = beam->get_grob_property ("positions");
1327 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1328 pos = ly_scm2interval (s);
1330 Real dy = pos.delta ();
1331 // ugh -> use commonx
1332 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1333 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1334 Real dydx = dy && dx ? dy/dx : 0;
1336 Direction d = Stem::get_direction (stem);
1337 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1339 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1342 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1345 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1347 minimum_dist + -d * (beamy - rest_dim) >? 0;
1349 int stafflines = Staff_symbol_referencer::line_count (rest);
1351 // move discretely by half spaces.
1352 int discrete_dist = int (ceil (dist));
1354 // move by whole spaces inside the staff.
1355 if (discrete_dist < stafflines+1)
1356 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1358 return gh_double2scm (-d * discrete_dist);
1364 ADD_INTERFACE (Beam, "beam-interface",
1367 #'thickness= weight of beams, in staffspace
1370 We take the least squares line through the ideal-length stems, and
1371 then damp that using
1373 damped = tanh (slope)
1375 this gives an unquantized left and right position for the beam end.
1376 Then we take all combinations of quantings near these left and right
1377 positions, and give them a score (according to how close they are to
1378 the ideal slope, how close the result is to the ideal stems, etc.). We
1379 take the best scoring combination.
1382 "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");