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).
29 #include <math.h> // tanh.
31 #include "molecule.hh"
32 #include "directional-element-interface.hh"
36 #include "least-squares.hh"
38 #include "paper-def.hh"
40 #include "group-interface.hh"
41 #include "staff-symbol-referencer.hh"
47 #define DEBUG_QUANTING 0
51 #include "text-item.hh" // debug output.
52 #include "font-interface.hh" // debug output.
56 const int INTER_QUANT_PENALTY = 1000;
57 const int SECONDARY_BEAM_DEMERIT = 15;
58 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
59 // possibly ridiculous, but too short stems just won't do
60 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
61 const int DAMPING_DIRECTIION_PENALTY = 800;
62 const int MUSICAL_DIRECTION_FACTOR = 400;
63 const int IDEAL_SLOPE_FACTOR = 10;
64 const int REGION_SIZE = 2;
68 shrink_extra_weight (Real x)
70 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
74 Beam::add_stem (Grob *me, Grob *s)
76 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
78 s->add_dependency (me);
80 assert (!Stem::beam_l (s));
81 s->set_grob_property ("beam", me->self_scm ());
83 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
87 Beam::get_interbeam (Grob *me)
89 SCM func = me->get_grob_property ("space-function");
90 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
91 return gh_scm2double (s);
98 Beam::get_multiplicity (Grob *me)
101 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
103 Grob *sc = unsmob_grob (ly_car (s));
105 if (Stem::has_interface (sc))
106 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
111 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
113 Beam::space_function (SCM smob, SCM multiplicity)
115 Grob *me = unsmob_grob (smob);
117 Real staff_space = Staff_symbol_referencer::staff_space (me);
118 Real line = me->paper_l ()->get_var ("linethickness");
119 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
122 Real interbeam = gh_scm2int (multiplicity) < 4
123 ? (2*staff_space + line - thickness) / 2.0
124 : (3*staff_space + line - thickness) / 3.0;
126 return gh_double2scm (interbeam);
130 /* After pre-processing all directions should be set.
131 Several post-processing routines (stem, slur, script) need stem/beam
133 Currenly, this means that beam has set all stem's directions.
134 [Alternatively, stems could set its own directions, according to
135 their beam, during 'final-pre-processing'.] */
136 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
138 Beam::before_line_breaking (SCM smob)
140 Grob *me = unsmob_grob (smob);
142 /* Beams with less than 2 two stems don't make much sense, but could happen
147 For a beam that only has one stem, we try to do some disappearance magic:
148 we revert the flag, and move on to The Eternal Engraving Fields. */
150 int count = visible_stem_count (me);
153 me->warning (_ ("beam has less than two visible stems"));
155 SCM stems = me->get_grob_property ("stems");
156 if (scm_ilength (stems) == 1)
158 me->warning (_ ("Beam has less than two stems. Removing beam."));
160 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
163 return SCM_UNSPECIFIED;
165 else if (scm_ilength (stems) == 0)
168 return SCM_UNSPECIFIED;
173 Direction d = get_default_dir (me);
175 consider_auto_knees (me, d);
176 set_stem_directions (me, d);
177 set_stem_shorten (me);
184 Beam::get_default_dir (Grob *me)
186 Drul_array<int> total;
187 total[UP] = total[DOWN] = 0;
188 Drul_array<int> count;
189 count[UP] = count[DOWN] = 0;
192 Link_array<Item> stems=
193 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
195 for (int i=0; i <stems.size (); i++)
198 Direction sd = Directional_element_interface::get (s);
200 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
201 int current = sd ? (1 + d * sd)/2 : center_distance;
208 } while (flip (&d) != DOWN);
210 SCM func = me->get_grob_property ("dir-function");
211 SCM s = gh_call2 (func,
212 gh_cons (gh_int2scm (count[UP]),
213 gh_int2scm (count[DOWN])),
214 gh_cons (gh_int2scm (total[UP]),
215 gh_int2scm (total[DOWN])));
217 if (gh_number_p (s) && gh_scm2int (s))
220 /* If dir is not determined: get default */
221 return to_dir (me->get_grob_property ("neutral-direction"));
225 /* Set all stems with non-forced direction to beam direction.
226 Urg: non-forced should become `without/with unforced' direction,
227 once stem gets cleaned-up. */
229 Beam::set_stem_directions (Grob *me, Direction d)
231 Link_array<Item> stems
232 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
234 for (int i=0; i <stems.size (); i++)
237 SCM force = s->remove_grob_property ("dir-forced");
238 if (!gh_boolean_p (force) || !gh_scm2bool (force))
239 Directional_element_interface::set (s, d);
243 /* Simplistic auto-knees; only consider vertical gap between two
246 `Forced' stem directions are ignored. If you don't want auto-knees,
247 don't set, or unset auto-knee-gap. */
249 Beam::consider_auto_knees (Grob *me, Direction d)
251 SCM scm = me->get_grob_property ("auto-knee-gap");
253 if (gh_number_p (scm))
257 Real staff_space = Staff_symbol_referencer::staff_space (me);
258 Real gap = gh_scm2double (scm) / staff_space;
261 Link_array<Item> stems=
262 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
264 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
265 for (int i=1; i < stems.size (); i++)
266 if (!Stem::invisible_b (stems[i]))
267 common = common->common_refpoint (stems[i], Y_AXIS);
270 for (int i=1; i < stems.size (); i++)
272 if (!Stem::invisible_b (stems[i-1]))
274 if (Stem::invisible_b (stems[l]))
276 if (Stem::invisible_b (stems[i]))
279 Real left = Stem::extremal_heads (stems[l])[d]
280 ->relative_coordinate (common, Y_AXIS);
281 Real right = Stem::extremal_heads (stems[i])[-d]
282 ->relative_coordinate (common, Y_AXIS);
284 Real dy = right - left;
288 knee_y = (right + left) / 2;
296 for (int i=0; i < stems.size (); i++)
298 if (Stem::invisible_b (stems[i]))
301 Real y = Stem::extremal_heads (stems[i])[d]
302 ->relative_coordinate (common, Y_AXIS);
304 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
305 s->set_grob_property ("dir-forced", SCM_BOOL_T);
311 /* Set stem's shorten property if unset.
314 take some y-position (chord/beam/nearest?) into account
315 scmify forced-fraction
319 why is shorten stored in beam, and not directly in stem?
323 Beam::set_stem_shorten (Grob *m)
325 Spanner*me = dynamic_cast<Spanner*> (m);
327 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
329 int multiplicity = get_multiplicity (me);
331 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
332 if (shorten == SCM_EOL)
335 int sz = scm_ilength (shorten);
337 Real staff_space = Staff_symbol_referencer::staff_space (me);
338 SCM shorten_elt = scm_list_ref (shorten,
339 gh_int2scm (multiplicity <? (sz - 1)));
340 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
342 /* your similar cute comment here */
343 shorten_f *= forced_fraction;
346 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
349 /* Call list of y-dy-callbacks, that handle setting of
353 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
355 Beam::after_line_breaking (SCM smob)
357 Grob *me = unsmob_grob (smob);
359 /* Copy to mutable list. */
360 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
361 me->set_grob_property ("positions", s);
363 if (ly_car (s) != SCM_BOOL_F)
364 return SCM_UNSPECIFIED;
366 // one wonders if such genericity is necessary --hwn.
367 SCM callbacks = me->get_grob_property ("position-callbacks");
368 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
369 gh_call1 (ly_car (i), smob);
371 set_stem_lengths (me);
372 return SCM_UNSPECIFIED;
386 - Make all demerits customisable
388 - One sensible check per demerit (what's this --hwn)
390 - Add demerits for quants per se, as to forbid a specific quant
394 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
396 Beam::quanting (SCM smob)
398 Grob *me = unsmob_grob (smob);
400 SCM s = me->get_grob_property ("positions");
401 Real yl = gh_scm2double (gh_car (s));
402 Real yr = gh_scm2double (gh_cdr (s));
404 Real ss = Staff_symbol_referencer::staff_space (me);
405 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
406 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
409 SCM sdy = me->get_grob_property ("least-squares-dy");
410 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
413 Real sit = (thickness - slt) / 2;
415 Real hang = 1.0 - (thickness - slt) / 2;
416 Real quants [] = {straddle, sit, inter, hang };
418 int num_quants = int (sizeof (quants)/sizeof (Real));
423 going to REGION_SIZE == 2, yields another 0.6 second with
427 (result indexes between 70 and 575) ? --hwn.
434 Do stem computations. These depend on YL and YR linearly, so we can
435 precompute for every stem 2 factors.
437 Link_array<Grob> stems=
438 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
439 Array<Stem_info> stem_infos;
440 Array<Real> lbase_lengths;
441 Array<Real> rbase_lengths;
443 Drul_array<bool> dirs_found(0,0);
444 for (int i= 0; i < stems.size(); i++)
447 stem_infos.push (Stem::calc_stem_info (s));
448 dirs_found[stem_infos.top ().dir_] = true;
450 Real b = calc_stem_y (me, s, Interval (1,0), false);
451 lbase_lengths.push (b);
453 Real a = calc_stem_y (me, s, Interval (0,1), false);
454 rbase_lengths.push (a);
457 Direction ldir = Direction (stem_infos[0].dir_);
458 Direction rdir = Direction (stem_infos.top ().dir_);
459 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
462 int region_size = REGION_SIZE;
464 Knees are harder, lets try some more possibilities for knees.
469 for (int i = -region_size ; i < region_size; i++)
470 for (int j = 0; j < num_quants; j++)
472 quantsl.push (i + quants[j] + int (yl));
473 quantsr.push (i + quants[j] + int (yr));
476 Array<Quant_score> qscores;
478 for (int l =0; l < quantsl.size (); l++)
479 for (int r =0; r < quantsr.size (); r++)
491 This is a longish function, but we don't separate this out into
492 neat modular separate subfunctions, as the subfunctions would be
493 called for many values of YL, YR. By precomputing various
494 parameters outside of the loop, we can save a lot of time.
497 for (int i = qscores.size (); i--;)
498 if (qscores[i].demerits < 100)
501 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
505 Real rad = Staff_symbol_referencer::staff_radius (me);
506 int multiplicity = get_multiplicity (me);
507 Real interbeam = multiplicity < 4
508 ? (2*ss + slt - thickness) / 2.0
509 : (3*ss + slt - thickness) / 3.0;
511 for (int i = qscores.size (); i--;)
512 if (qscores[i].demerits < 100)
515 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
516 rad, slt, thickness, interbeam,
517 multiplicity, ldir, rdir);
521 for (int i = qscores.size (); i--;)
522 if (qscores[i].demerits < 100)
525 += score_stem_lengths (stems, stem_infos,
526 lbase_lengths, rbase_lengths,
528 me, qscores[i].yl, qscores[i].yr);
534 for (int i = qscores.size (); i--;)
536 if (qscores[i].demerits < best)
538 best = qscores [i].demerits ;
544 me->set_grob_property ("positions",
545 gh_cons (gh_double2scm (qscores[best_idx].yl),
546 gh_double2scm (qscores[best_idx].yr))
552 me->set_grob_property ("quant-score",
553 gh_double2scm (qscores[best_idx].demerits));
554 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
557 return SCM_UNSPECIFIED;
561 Beam::score_stem_lengths (Link_array<Grob>stems,
562 Array<Stem_info> stem_infos,
563 Array<Real> left_factor,
564 Array<Real> right_factor,
569 Real demerit_score = 0.0 ;
570 Real pen = STEM_LENGTH_LIMIT_PENALTY;
572 for (int i=0; i < stems.size (); i++)
575 if (Stem::invisible_b (s))
579 yl * left_factor[i] + right_factor[i]* yr;
581 Stem_info info = stem_infos[i];
582 Direction d = info.dir_;
585 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
587 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
588 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
591 demerit_score *= 2.0 / stems.size ();
593 return demerit_score;
597 Beam::score_slopes_dy (Grob *me,
599 Real dy_mus, Real dy_damp)
604 if (sign (dy_damp) != sign (dy))
606 dem += DAMPING_DIRECTIION_PENALTY;
609 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
610 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
618 return x - floor (x);
622 Beam::score_forbidden_quants (Grob*me,
626 Real thickness, Real interbeam,
628 Direction ldir, Direction rdir)
633 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
634 dem += INTER_QUANT_PENALTY;
635 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
636 dem += INTER_QUANT_PENALTY;
638 // todo: use multiplicity of outer stems.
639 if (multiplicity >= 2)
643 Real sit = (thickness - slt) / 2;
645 Real hang = 1.0 - (thickness - slt) / 2;
648 if (fabs (yl - ldir * interbeam) < rad
649 && fabs (my_modf (yl) - inter) < 1e-3)
650 dem += SECONDARY_BEAM_DEMERIT;
651 if (fabs (yr - rdir * interbeam) < rad
652 && fabs (my_modf (yr) - inter) < 1e-3)
653 dem += SECONDARY_BEAM_DEMERIT;
658 Can't we simply compute the distance between the nearest
659 staffline and the secondary beam? That would get rid of the
660 silly case analysis here (which is probably not when we have
661 different beam-thicknesses.)
667 // hmm, without Interval/Drul_array, you get ~ 4x same code...
668 if (fabs (yl - ldir * interbeam) < rad + inter)
670 if (ldir == UP && dy <= eps
671 && fabs (my_modf (yl) - sit) < eps)
672 dem += SECONDARY_BEAM_DEMERIT;
674 if (ldir == DOWN && dy >= eps
675 && fabs (my_modf (yl) - hang) < eps)
676 dem += SECONDARY_BEAM_DEMERIT;
679 if (fabs (yr - rdir * interbeam) < rad + inter)
681 if (rdir == UP && dy >= eps
682 && fabs (my_modf (yr) - sit) < eps)
683 dem += SECONDARY_BEAM_DEMERIT;
685 if (rdir == DOWN && dy <= eps
686 && fabs (my_modf (yr) - hang) < eps)
687 dem += SECONDARY_BEAM_DEMERIT;
690 if (multiplicity >= 3)
692 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
694 if (ldir == UP && dy <= eps
695 && fabs (my_modf (yl) - straddle) < eps)
696 dem += SECONDARY_BEAM_DEMERIT;
698 if (ldir == DOWN && dy >= eps
699 && fabs (my_modf (yl) - straddle) < eps)
700 dem += SECONDARY_BEAM_DEMERIT;
703 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
705 if (rdir == UP && dy >= eps
706 && fabs (my_modf (yr) - straddle) < eps)
707 dem += SECONDARY_BEAM_DEMERIT;
709 if (rdir == DOWN && dy <= eps
710 && fabs (my_modf (yr) - straddle) < eps)
711 dem += SECONDARY_BEAM_DEMERIT;
721 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
723 Beam::least_squares (SCM smob)
725 Grob *me = unsmob_grob (smob);
727 int count = visible_stem_count (me);
732 me->set_grob_property ("positions", ly_interval2scm (pos));
733 return SCM_UNSPECIFIED;
736 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
737 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
741 Array<Real> x_posns ;
742 Link_array<Item> stems=
743 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
744 Grob *common = stems[0];
745 for (int i=1; i < stems.size (); i++)
746 common = stems[i]->common_refpoint (common, X_AXIS);
748 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
749 for (int i=0; i < stems.size (); i++)
753 Real x = s->relative_coordinate (common, X_AXIS) - x0;
756 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
764 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
765 Stem::chord_start_y (last_visible_stem (me)));
769 TODO -- use scoring for this.
771 complicated, because we take stem-info.ideal for determining
774 /* Make simple beam on middle line have small tilt */
775 if (!ideal[LEFT] && chord.delta () && count == 2)
781 Direction d = (Direction) (sign (chord.delta ()) * UP);
782 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
797 Array<Offset> ideals;
798 for (int i=0; i < stems.size (); i++)
801 if (Stem::invisible_b (s))
803 ideals.push (Offset (x_posns[i],
804 Stem::calc_stem_info (s).ideal_y_));
806 minimise_least_squares (&dydx, &y, ideals);
809 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
810 pos = Interval (y, (y+dy));
813 me->set_grob_property ("positions", ly_interval2scm (pos));
815 return SCM_UNSPECIFIED;
820 We can't combine with previous function, since check concave and
821 slope damping comes first.
823 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
825 Beam::shift_region_to_valid (SCM grob)
827 Grob *me = unsmob_grob (grob);
831 Array<Real> x_posns ;
832 Link_array<Item> stems=
833 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
834 Grob *common = stems[0];
835 for (int i=1; i < stems.size (); i++)
836 common = stems[i]->common_refpoint (common, X_AXIS);
838 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
839 for (int i=0; i < stems.size (); i++)
843 Real x = s->relative_coordinate (common, X_AXIS) - x0;
846 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
848 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
849 Real dy = pos.delta();
855 Shift the positions so that we have a chance of finding good
856 quants (i.e. no short stem failures.)
858 Interval feasible_left_point;
859 feasible_left_point.set_full ();
860 for (int i=0; i < stems.size (); i++)
863 if (Stem::invisible_b (s))
867 Direction d = Stem::get_direction (s);
871 TODO: use real beam space function
873 Real left_y = Stem::calc_stem_info (s).shortest_y_
874 - dydx * (x_posns [i] - x0);
880 feasible_left_point.intersect (flp);
883 if (feasible_left_point.empty_b())
885 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
887 else if (!feasible_left_point.elem_b(y))
889 if (isinf (feasible_left_point[DOWN]))
890 y = feasible_left_point[UP] - REGION_SIZE;
891 else if (isinf (feasible_left_point[UP]))
892 y = feasible_left_point[DOWN]+ REGION_SIZE;
894 y = feasible_left_point.center ();
896 pos = Interval (y, (y+dy));
897 me->set_grob_property ("positions", ly_interval2scm (pos));
898 return SCM_UNSPECIFIED;
902 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
904 Beam::check_concave (SCM smob)
906 Grob *me = unsmob_grob (smob);
908 Link_array<Item> stems =
909 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
911 for (int i = 0; i < stems.size ();)
913 if (Stem::invisible_b (stems[i]))
919 if (stems.size () < 3)
920 return SCM_UNSPECIFIED;
923 /* Concaveness #1: If distance of an inner notehead to line between
924 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
925 beam is concave (Heinz Stolba).
927 In the case of knees, the line connecting outer heads is often
928 not related to the beam slope (it may even go in the other
929 direction). Skip the check when the outer stems point in
930 different directions. --hwn
933 bool concaveness1 = false;
934 SCM gap = me->get_grob_property ("concaveness-gap");
935 if (gh_number_p (gap)
936 && Stem::get_direction(stems.top ())
937 == Stem::get_direction(stems[0]))
939 Real r1 = gh_scm2double (gap);
940 Real dy = Stem::chord_start_y (stems.top ())
941 - Stem::chord_start_y (stems[0]);
944 Real slope = dy / (stems.size () - 1);
946 Real y0 = Stem::chord_start_y (stems[0]);
947 for (int i = 1; i < stems.size () - 1; i++)
949 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
959 /* Concaveness #2: Sum distances of inner noteheads that fall
960 outside the interval of the two outer noteheads.
962 We only do this for beams where first and last stem have the same
966 Note that "convex" stems compensate for "concave" stems.
967 (is that intentional?) --hwn.
970 Real concaveness2 = 0;
971 SCM thresh = me->get_grob_property ("concaveness-threshold");
972 Real r2 = infinity_f;
973 if (!concaveness1 && gh_number_p (thresh)
974 && Stem::get_direction(stems.top ())
975 == Stem::get_direction(stems[0]))
977 r2 = gh_scm2double (thresh);
979 Direction dir = Stem::get_direction(stems.top ());
981 Interval iv (Stem::chord_start_y (stems[0]),
982 Stem::chord_start_y (stems.top ()));
984 if (iv[MAX] < iv[MIN])
987 for (int i = 1; i < stems.size () - 1; i++)
989 Real f = Stem::chord_start_y (stems[i]);
990 concave += ((f - iv[MAX] ) >? 0) +
991 ((f - iv[MIN] ) <? 0);
994 concaveness2 = concave / (stems.size () - 2);
996 /* ugh: this is the a kludge to get
997 input/regression/beam-concave.ly to behave as
1001 huh? we're dividing twice (which is not scalable) meaning that
1002 the longer the beam, the more unlikely it will be
1003 concave. Maybe you would even expect the other way around??
1008 concaveness2 /= (stems.size () - 2);
1011 /* TODO: some sort of damping iso -> plain horizontal */
1012 if (concaveness1 || concaveness2 > r2)
1014 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1015 Real r = pos.linear_combination (0);
1016 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1017 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1020 return SCM_UNSPECIFIED;
1023 /* This neat trick is by Werner Lemberg,
1024 damped = tanh (slope)
1025 corresponds with some tables in [Wanske] CHECKME */
1026 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1028 Beam::slope_damping (SCM smob)
1030 Grob *me = unsmob_grob (smob);
1032 if (visible_stem_count (me) <= 1)
1033 return SCM_UNSPECIFIED;
1035 SCM s = me->get_grob_property ("damping");
1036 int damping = gh_scm2int (s);
1040 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1041 Real dy = pos.delta ();
1043 // ugh -> use commonx
1044 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
1045 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1046 Real dydx = dy && dx ? dy/dx : 0;
1047 dydx = 0.6 * tanh (dydx) / damping;
1049 Real damped_dy = dydx * dx;
1050 pos[LEFT] += (dy - damped_dy) / 2;
1051 pos[RIGHT] -= (dy - damped_dy) / 2;
1053 me->set_grob_property ("positions", ly_interval2scm (pos));
1055 return SCM_UNSPECIFIED;
1059 Calculate the Y position of the stem-end, given the Y-left, Y-right
1060 in POS, and for stem S.
1062 If CORRECT, correct for multiplicity of beam in case of knees.
1065 TODO: junk CORRECT from this.
1068 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool correct)
1070 int beam_multiplicity = get_multiplicity (me);
1071 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
1074 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1075 Real interbeam = get_interbeam (me);
1077 // ugh -> use commonx
1078 Grob * fvs = first_visible_stem (me);
1079 Grob *lvs = last_visible_stem (me);
1081 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1082 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1083 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1084 Real dy = pos.delta ();
1085 Real stem_y = (dy && dx
1090 Direction my_dir = Directional_element_interface::get (s);
1091 Direction first_dir = fvs? Directional_element_interface::get (fvs) : my_dir;
1093 if (correct && my_dir != first_dir)
1096 WTF is happening here ?
1098 It looks as if this is some kind of fixup for multiple kneed
1099 beams to get a piece of stem at the #.
1110 Rules for this kind of stuff are hairy. In any event, the
1111 current stem should look at the multiplicity of its
1118 // FIXME, hairy stuff
1119 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1121 // huh, why not for first visible?
1124 What the heck is happening here??
1126 Grob *last_visible = last_visible_stem (me);
1129 if ( Staff_symbol_referencer::staff_symbol_l (s)
1130 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1131 stem_y += Directional_element_interface::get (me)
1132 * (beam_multiplicity - stem_multiplicity) * interbeam;
1135 programming_error ("No last visible stem");
1141 Hmm. At this time, beam position and slope are determined. Maybe,
1142 stem directions and length should set to relative to the chord's
1143 position of the beam. */
1145 Beam::set_stem_lengths (Grob *me)
1147 Link_array<Item> stems=
1148 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1150 if (stems.size () <= 1)
1153 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1154 for (int i=1; i < stems.size (); i++)
1155 if (!Stem::invisible_b (stems[i]))
1156 common = common->common_refpoint (stems[i], Y_AXIS);
1158 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1159 Real staff_space = Staff_symbol_referencer::staff_space (me);
1165 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1166 Direction dir = Directional_element_interface::get (me);
1167 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1170 for (int i=0; i < stems.size (); i++)
1173 if (Stem::invisible_b (s))
1176 Real stem_y = calc_stem_y (me, s, pos, true);
1179 // doesn't play well with dvips
1181 if (Stem::get_direction (s) == dir)
1182 stem_y += Stem::get_direction (s) * thick / 2;
1185 /* caution: stem measures in staff-positions */
1186 Real id = me->relative_coordinate (common, Y_AXIS)
1187 - stems[i]->relative_coordinate (common, Y_AXIS);
1188 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1193 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1195 Link_array<Grob> stems=
1196 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1199 for (int i=0; i < stems.size (); i++)
1203 /* Don't overwrite user override (?) */
1204 if (Stem::beam_count (stems[i], d) == -1
1205 /* Don't set beaming for outside of outer stems */
1206 && ! (d == LEFT && i == 0)
1207 && ! (d == RIGHT && i == stems.size () -1))
1209 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1210 Stem::set_beaming (stems[i], b, d);
1213 while (flip (&d) != LEFT);
1220 beams to go with one stem.
1224 The beam should be constructed by one function that knows where the
1225 X and Y points are, and only inspects the stems to obtain
1226 multiplicity and stem directions.
1230 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1232 // ugh -> use commonx
1234 && ! (next->relative_coordinate (0, X_AXIS)
1235 > here->relative_coordinate (0, X_AXIS)))
1237 && ! (prev->relative_coordinate (0, X_AXIS)
1238 < here->relative_coordinate (0, X_AXIS))))
1239 programming_error ("Beams are not left-to-right");
1241 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1242 Real bdy = get_interbeam (me);
1245 Molecule rightbeams;
1248 if (!Stem::first_head (here))
1252 int t = Stem::duration_log (here);
1254 SCM proc = me->get_grob_property ("flag-width-function");
1255 SCM result = gh_call1 (proc, gh_int2scm (t));
1256 nw_f = gh_scm2double (result);
1260 /* [Tremolo] beams on whole notes may not have direction set? */
1261 Direction dir = Directional_element_interface::get (here);
1263 /* half beams extending to the left. */
1266 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1267 - Stem::beam_count (prev, RIGHT);
1268 int lwholebeams= Stem::beam_count (here, LEFT)
1269 <? Stem::beam_count (prev, RIGHT);
1271 /* Half beam should be one note-width,
1272 but let's make sure two half-beams never touch */
1274 // FIXME: TODO (check) stem width / sloped beams
1275 Real w = here->relative_coordinate (0, X_AXIS)
1276 - prev->relative_coordinate (0, X_AXIS);
1277 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1279 * me->paper_l ()->get_var ("linethickness");
1283 if (lhalfs) // generates warnings if not
1284 a = Lookup::beam (dydx, w + stem_w, thick);
1285 a.translate (Offset (-w, -w * dydx));
1286 a.translate_axis (-stem_w/2, X_AXIS);
1287 for (int j = 0; j < lhalfs; j++)
1290 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1291 leftbeams.add_molecule (b);
1297 int rhalfs = Stem::beam_count (here, RIGHT)
1298 - Stem::beam_count (next, LEFT);
1299 int rwholebeams= Stem::beam_count (here, RIGHT)
1300 <? Stem::beam_count (next, LEFT);
1302 Real w = next->relative_coordinate (0, X_AXIS)
1303 - here->relative_coordinate (0, X_AXIS);
1305 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1307 * me->paper_l ()->get_var ("linethickness");
1309 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1310 a.translate_axis (- stem_w/2, X_AXIS);
1314 SCM gap = me->get_grob_property ("gap");
1315 if (gh_number_p (gap))
1317 int gap_i = gh_scm2int ((gap));
1318 int nogap = rwholebeams - gap_i;
1320 for (; j < nogap; j++)
1323 b.translate_axis (-dir * bdy * j, Y_AXIS);
1324 rightbeams.add_molecule (b);
1326 if (Stem::invisible_b (here))
1331 a = Lookup::beam (dydx, w + stem_w, thick);
1334 for (; j < rwholebeams; j++)
1338 if (Stem::invisible_b (here))
1339 // ugh, see chord-tremolo.ly
1340 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1343 b.translate (Offset (tx, -dir * bdy * j));
1344 rightbeams.add_molecule (b);
1349 a = Lookup::beam (dydx, w, thick);
1351 for (; j < rwholebeams + rhalfs; j++)
1354 b.translate_axis (- dir * bdy * j, Y_AXIS);
1355 rightbeams.add_molecule (b);
1359 leftbeams.add_molecule (rightbeams);
1365 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1367 Beam::brew_molecule (SCM smob)
1369 Grob *me =unsmob_grob (smob);
1372 if (!gh_pair_p (me->get_grob_property ("stems")))
1375 Link_array<Item>stems =
1376 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1377 if (visible_stem_count (me))
1379 // ugh -> use commonx
1380 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1381 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1385 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1386 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1389 SCM posns = me->get_grob_property ("positions");
1391 if (!ly_number_pair_p (posns))
1393 programming_error ("No beam posns");
1394 pos = Interval (0,0);
1397 pos= ly_scm2interval (posns);
1398 Real dy = pos.delta ();
1399 Real dydx = dy && dx ? dy/dx : 0;
1402 for (int i=0; i < stems.size (); i++)
1404 Item *item = stems[i];
1405 Item *prev = (i > 0)? stems[i-1] : 0;
1406 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1410 Molecule sb = stem_beams (me, item, next, prev, dydx);
1411 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1412 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1415 mol.add_molecule (sb);
1418 mol.translate_axis (x0
1419 - dynamic_cast<Spanner*> (me)
1420 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1423 #if (DEBUG_QUANTING)
1426 This code prints the demerits for each beam. Perhaps this
1427 should be switchable for those who want to twiddle with the
1433 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1436 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1439 SCM properties = Font_interface::font_alist_chain (me);
1442 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1443 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1447 return mol.smobbed_copy ();
1451 Beam::forced_stem_count (Grob *me)
1453 Link_array<Item>stems =
1454 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1456 for (int i=0; i < stems.size (); i++)
1460 if (Stem::invisible_b (s))
1463 if (((int)Stem::chord_start_y (s))
1464 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1474 Beam::visible_stem_count (Grob *me)
1476 Link_array<Item>stems =
1477 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1479 for (int i = stems.size (); i--;)
1481 if (!Stem::invisible_b (stems[i]))
1488 Beam::first_visible_stem (Grob *me)
1490 Link_array<Item>stems =
1491 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1493 for (int i = 0; i < stems.size (); i++)
1495 if (!Stem::invisible_b (stems[i]))
1502 Beam::last_visible_stem (Grob *me)
1504 Link_array<Item>stems =
1505 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1506 for (int i = stems.size (); i--;)
1508 if (!Stem::invisible_b (stems[i]))
1518 handle rest under beam (do_post: beams are calculated now)
1519 what about combination of collisions and rest under beam.
1523 rest -> stem -> beam -> interpolate_y_position ()
1525 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1527 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1529 Grob *rest = unsmob_grob (element_smob);
1530 Axis a = (Axis) gh_scm2int (axis);
1532 assert (a == Y_AXIS);
1534 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1537 return gh_double2scm (0.0);
1538 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1540 || !Beam::has_interface (beam)
1541 || !Beam::visible_stem_count (beam))
1542 return gh_double2scm (0.0);
1544 // make callback for rest from this.
1545 // todo: make sure this calced already.
1547 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1548 Interval pos (0, 0);
1549 SCM s = beam->get_grob_property ("positions");
1550 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1551 pos = ly_scm2interval (s);
1553 Real dy = pos.delta ();
1554 // ugh -> use commonx
1555 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1556 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1557 Real dydx = dy && dx ? dy/dx : 0;
1559 Direction d = Stem::get_direction (stem);
1560 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1562 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1565 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1568 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1570 minimum_dist + -d * (beamy - rest_dim) >? 0;
1572 int stafflines = Staff_symbol_referencer::line_count (rest);
1574 // move discretely by half spaces.
1575 int discrete_dist = int (ceil (dist));
1577 // move by whole spaces inside the staff.
1578 if (discrete_dist < stafflines+1)
1579 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1581 return gh_double2scm (-d * discrete_dist);
1587 ADD_INTERFACE (Beam, "beam-interface",
1590 #'thickness= weight of beams, in staffspace
1593 We take the least squares line through the ideal-length stems, and
1594 then damp that using
1596 damped = tanh (slope)
1598 this gives an unquantized left and right position for the beam end.
1599 Then we take all combinations of quantings near these left and right
1600 positions, and give them a score (according to how close they are to
1601 the ideal slope, how close the result is to the ideal stems, etc.). We
1602 take the best scoring combination.
1605 "position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy damping flag-width-function neutral-direction positions space-function thickness");