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 /* snapnie now also works */
31 #include <math.h> // tanh.
33 #include "molecule.hh"
34 #include "directional-element-interface.hh"
38 #include "least-squares.hh"
40 #include "paper-def.hh"
42 #include "group-interface.hh"
43 #include "staff-symbol-referencer.hh"
49 #define DEBUG_QUANTING 0
53 #include "text-item.hh" // debug output.
54 #include "font-interface.hh" // debug output.
58 const int INTER_QUANT_PENALTY = 1000;
59 const int SECONDARY_BEAM_DEMERIT = 15;
60 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
61 // possibly ridiculous, but too short stems just won't do
62 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
63 const int DAMPING_DIRECTIION_PENALTY = 800;
64 const int MUSICAL_DIRECTION_FACTOR = 400;
65 const int IDEAL_SLOPE_FACTOR = 10;
69 shrink_extra_weight (Real x)
71 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
75 Beam::add_stem (Grob *me, Grob *s)
77 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
79 s->add_dependency (me);
81 assert (!Stem::beam_l (s));
82 s->set_grob_property ("beam", me->self_scm ());
84 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
88 Beam::get_interbeam (Grob *me)
90 SCM func = me->get_grob_property ("space-function");
91 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
92 return gh_scm2double (s);
99 Beam::get_multiplicity (Grob *me)
102 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
104 Grob *sc = unsmob_grob (ly_car (s));
106 if (Stem::has_interface (sc))
107 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
112 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
114 Beam::space_function (SCM smob, SCM multiplicity)
116 Grob *me = unsmob_grob (smob);
118 Real staff_space = Staff_symbol_referencer::staff_space (me);
119 Real line = me->paper_l ()->get_var ("linethickness");
120 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
123 Real interbeam = gh_scm2int (multiplicity) < 4
124 ? (2*staff_space + line - thickness) / 2.0
125 : (3*staff_space + line - thickness) / 3.0;
127 return gh_double2scm (interbeam);
131 /* After pre-processing all directions should be set.
132 Several post-processing routines (stem, slur, script) need stem/beam
134 Currenly, this means that beam has set all stem's directions.
135 [Alternatively, stems could set its own directions, according to
136 their beam, during 'final-pre-processing'.] */
137 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
139 Beam::before_line_breaking (SCM smob)
141 Grob *me = unsmob_grob (smob);
143 /* Beams with less than 2 two stems don't make much sense, but could happen
148 For a beam that only has one stem, we try to do some disappearance magic:
149 we revert the flag, and move on to The Eternal Engraving Fields. */
151 int count = visible_stem_count (me);
154 me->warning (_ ("beam has less than two visible stems"));
156 SCM stems = me->get_grob_property ("stems");
157 if (scm_ilength (stems) == 1)
159 me->warning (_ ("Beam has less than two stems. Removing beam."));
161 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
164 return SCM_UNSPECIFIED;
166 else if (scm_ilength (stems) == 0)
169 return SCM_UNSPECIFIED;
174 Direction d = get_default_dir (me);
176 consider_auto_knees (me, d);
177 set_stem_directions (me, d);
178 set_stem_shorten (me);
185 Beam::get_default_dir (Grob *me)
187 Drul_array<int> total;
188 total[UP] = total[DOWN] = 0;
189 Drul_array<int> count;
190 count[UP] = count[DOWN] = 0;
193 Link_array<Item> stems=
194 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
196 for (int i=0; i <stems.size (); i++)
199 Direction sd = Directional_element_interface::get (s);
201 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
202 int current = sd ? (1 + d * sd)/2 : center_distance;
209 } while (flip (&d) != DOWN);
211 SCM func = me->get_grob_property ("dir-function");
212 SCM s = gh_call2 (func,
213 gh_cons (gh_int2scm (count[UP]),
214 gh_int2scm (count[DOWN])),
215 gh_cons (gh_int2scm (total[UP]),
216 gh_int2scm (total[DOWN])));
218 if (gh_number_p (s) && gh_scm2int (s))
221 /* If dir is not determined: get default */
222 return to_dir (me->get_grob_property ("neutral-direction"));
226 /* Set all stems with non-forced direction to beam direction.
227 Urg: non-forced should become `without/with unforced' direction,
228 once stem gets cleaned-up. */
230 Beam::set_stem_directions (Grob *me, Direction d)
232 Link_array<Item> stems
233 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
235 for (int i=0; i <stems.size (); i++)
238 SCM force = s->remove_grob_property ("dir-forced");
239 if (!gh_boolean_p (force) || !gh_scm2bool (force))
240 Directional_element_interface::set (s, d);
244 /* Simplistic auto-knees; only consider vertical gap between two
247 `Forced' stem directions are ignored. If you don't want auto-knees,
248 don't set, or unset auto-knee-gap. */
250 Beam::consider_auto_knees (Grob *me, Direction d)
252 SCM scm = me->get_grob_property ("auto-knee-gap");
254 if (gh_number_p (scm))
258 Real staff_space = Staff_symbol_referencer::staff_space (me);
259 Real gap = gh_scm2double (scm) / staff_space;
262 Link_array<Item> stems=
263 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
265 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
266 for (int i=1; i < stems.size (); i++)
267 if (!Stem::invisible_b (stems[i]))
268 common = common->common_refpoint (stems[i], Y_AXIS);
271 for (int i=1; i < stems.size (); i++)
273 if (!Stem::invisible_b (stems[i-1]))
275 if (Stem::invisible_b (stems[l]))
277 if (Stem::invisible_b (stems[i]))
280 Real left = Stem::extremal_heads (stems[l])[d]
281 ->relative_coordinate (common, Y_AXIS);
282 Real right = Stem::extremal_heads (stems[i])[-d]
283 ->relative_coordinate (common, Y_AXIS);
285 Real dy = right - left;
289 knee_y = (right + left) / 2;
297 for (int i=0; i < stems.size (); i++)
299 if (Stem::invisible_b (stems[i]))
302 Real y = Stem::extremal_heads (stems[i])[d]
303 ->relative_coordinate (common, Y_AXIS);
305 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
306 s->set_grob_property ("dir-forced", SCM_BOOL_T);
312 /* Set stem's shorten property if unset.
315 take some y-position (chord/beam/nearest?) into account
316 scmify forced-fraction
320 why is shorten stored in beam, and not directly in stem?
324 Beam::set_stem_shorten (Grob *m)
326 Spanner*me = dynamic_cast<Spanner*> (m);
328 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
330 int multiplicity = get_multiplicity (me);
332 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
333 if (shorten == SCM_EOL)
336 int sz = scm_ilength (shorten);
338 Real staff_space = Staff_symbol_referencer::staff_space (me);
339 SCM shorten_elt = scm_list_ref (shorten,
340 gh_int2scm (multiplicity <? (sz - 1)));
341 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
343 /* your similar cute comment here */
344 shorten_f *= forced_fraction;
347 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
350 /* Call list of y-dy-callbacks, that handle setting of
351 grob-properties y, dy.
353 User may set grob-properties: y-position-hs and height-hs
354 (to be fixed) that override the calculated y and dy.
356 Because y and dy cannot be calculated and quanted separately, we
357 always calculate both, then check for user override. */
358 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
360 Beam::after_line_breaking (SCM smob)
362 Grob *me = unsmob_grob (smob);
364 /* Copy to mutable list. */
365 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
366 me->set_grob_property ("positions", s);
368 if (ly_car (s) != SCM_BOOL_F)
369 return SCM_UNSPECIFIED;
371 // one wonders if such genericity is necessary --hwn.
372 SCM callbacks = me->get_grob_property ("position-callbacks");
373 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
374 gh_call1 (ly_car (i), smob);
376 set_stem_lengths (me);
377 return SCM_UNSPECIFIED;
391 - Make all demerits customisable
393 - One sensible check per demerit (what's this --hwn)
395 - Add demerits for quants per se, as to forbid a specific quant
399 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
401 Beam::quanting (SCM smob)
403 Grob *me = unsmob_grob (smob);
405 SCM s = me->get_grob_property ("positions");
406 Real yl = gh_scm2double (gh_car (s));
407 Real yr = gh_scm2double (gh_cdr (s));
409 Real ss = Staff_symbol_referencer::staff_space (me);
410 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
411 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
414 SCM sdy = me->get_grob_property ("least-squares-dy");
415 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
418 Real sit = (thickness - slt) / 2;
420 Real hang = 1.0 - (thickness - slt) / 2;
421 Real quants [] = {straddle, sit, inter, hang };
423 int num_quants = int (sizeof (quants)/sizeof (Real));
428 going to REGION_SIZE == 2, yields another 0.6 second with
432 (result indexes between 70 and 575) ? --hwn.
439 Do stem computations. These depend on YL and YR linearly, so we can
440 precompute for every stem 2 factors.
442 Link_array<Grob> stems=
443 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
444 Array<Stem_info> stem_infos;
445 Array<Real> lbase_lengths;
446 Array<Real> rbase_lengths;
448 Drul_array<bool> dirs_found(0,0);
449 for (int i= 0; i < stems.size(); i++)
452 stem_infos.push (Stem::calc_stem_info (s));
453 dirs_found[stem_infos.top ().dir_] = true;
456 Real b = calc_stem_y (me, s, Interval (1,0), false);
457 lbase_lengths.push (b);
459 Real a = calc_stem_y (me, s, Interval (0,1), false);
460 rbase_lengths.push (a);
464 Direction ldir = Direction (stem_infos[0].dir_);
465 Direction rdir = Direction (stem_infos.top ().dir_);
466 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
472 Knees are harder, lets try some more possibilities for knees.
477 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
478 for (int j = 0; j < num_quants; j++)
480 quantsl.push (i + quants[j] + int (yl));
481 quantsr.push (i + quants[j] + int (yr));
484 Array<Quant_score> qscores;
486 for (int l =0; l < quantsl.size (); l++)
487 for (int r =0; r < quantsr.size (); r++)
499 This is a longish function, but we don't separate this out into
500 neat modular separate subfunctions, as the subfunctions would be
501 called for many values of YL, YR. By precomputing various
502 parameters outside of the loop, we can save a lot of time.
505 for (int i = qscores.size (); i--;)
506 if (qscores[i].demerits < 100)
509 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
513 Real rad = Staff_symbol_referencer::staff_radius (me);
514 int multiplicity = get_multiplicity (me);
515 Real interbeam = multiplicity < 4
516 ? (2*ss + slt - thickness) / 2.0
517 : (3*ss + slt - thickness) / 3.0;
519 for (int i = qscores.size (); i--;)
520 if (qscores[i].demerits < 100)
523 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
524 rad, slt, thickness, interbeam,
525 multiplicity, ldir, rdir);
529 for (int i = qscores.size (); i--;)
530 if (qscores[i].demerits < 100)
533 += score_stem_lengths (stems, stem_infos,
534 lbase_lengths, rbase_lengths,
536 me, qscores[i].yl, qscores[i].yr);
542 for (int i = qscores.size (); i--;)
544 if (qscores[i].demerits < best)
546 best = qscores [i].demerits ;
552 me->set_grob_property ("positions",
553 gh_cons (gh_double2scm (qscores[best_idx].yl),
554 gh_double2scm (qscores[best_idx].yr))
560 me->set_grob_property ("quant-score",
561 gh_double2scm (qscores[best_idx].demerits));
562 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
565 return SCM_UNSPECIFIED;
569 Beam::score_stem_lengths (Link_array<Grob>stems,
570 Array<Stem_info> stem_infos,
571 Array<Real> left_factor,
572 Array<Real> right_factor,
577 Real demerit_score = 0.0 ;
578 Real pen = STEM_LENGTH_LIMIT_PENALTY;
585 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
586 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
588 for (int i=0; i < stems.size (); i++)
591 if (Stem::invisible_b (s))
595 /* for a two-stemmed, interstaff beam knee up/down:
598 \context PianoStaff \notes\relative c' <
599 \context Staff = lh {
600 \stemDown [c8 \translator Staff = rh \stemUp a'' ]
602 \context Staff = rh \relative c' s4
606 with yl = -5.8 (about ideal)
607 and yr = -1 (ridiculous pos)
608 this yields current_y = -8.1 (about ideal) */
611 yl * left_factor[i] + right_factor[i]* yr;
613 Real f = (s->relative_coordinate (0, X_AXIS) - x0) / dx;
614 Real current_y = yl + f * (yr - yl);
617 Stem_info info = stem_infos[i];
618 Direction d = info.dir_;
621 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
623 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
624 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
627 demerit_score *= 2.0 / stems.size ();
629 return demerit_score;
633 Beam::score_slopes_dy (Grob *me,
635 Real dy_mus, Real dy_damp)
640 if (sign (dy_damp) != sign (dy))
642 dem += DAMPING_DIRECTIION_PENALTY;
645 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
646 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
654 return x - floor (x);
658 Beam::score_forbidden_quants (Grob*me,
662 Real thickness, Real interbeam,
664 Direction ldir, Direction rdir)
669 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
670 dem += INTER_QUANT_PENALTY;
671 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
672 dem += INTER_QUANT_PENALTY;
674 // todo: use multiplicity of outer stems.
675 if (multiplicity >= 2)
679 Real sit = (thickness - slt) / 2;
681 Real hang = 1.0 - (thickness - slt) / 2;
684 if (fabs (yl - ldir * interbeam) < rad
685 && fabs (my_modf (yl) - inter) < 1e-3)
686 dem += SECONDARY_BEAM_DEMERIT;
687 if (fabs (yr - rdir * interbeam) < rad
688 && fabs (my_modf (yr) - inter) < 1e-3)
689 dem += SECONDARY_BEAM_DEMERIT;
694 Can't we simply compute the distance between the nearest
695 staffline and the secondary beam? That would get rid of the
696 silly case analysis here (which is probably not when we have
697 different beam-thicknesses.)
703 // hmm, without Interval/Drul_array, you get ~ 4x same code...
704 if (fabs (yl - ldir * interbeam) < rad + inter)
706 if (ldir == UP && dy <= eps
707 && fabs (my_modf (yl) - sit) < eps)
708 dem += SECONDARY_BEAM_DEMERIT;
710 if (ldir == DOWN && dy >= eps
711 && fabs (my_modf (yl) - hang) < eps)
712 dem += SECONDARY_BEAM_DEMERIT;
715 if (fabs (yr - rdir * interbeam) < rad + inter)
717 if (rdir == UP && dy >= eps
718 && fabs (my_modf (yr) - sit) < eps)
719 dem += SECONDARY_BEAM_DEMERIT;
721 if (rdir == DOWN && dy <= eps
722 && fabs (my_modf (yr) - hang) < eps)
723 dem += SECONDARY_BEAM_DEMERIT;
726 if (multiplicity >= 3)
728 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
730 if (ldir == UP && dy <= eps
731 && fabs (my_modf (yl) - straddle) < eps)
732 dem += SECONDARY_BEAM_DEMERIT;
734 if (ldir == DOWN && dy >= eps
735 && fabs (my_modf (yl) - straddle) < eps)
736 dem += SECONDARY_BEAM_DEMERIT;
739 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
741 if (rdir == UP && dy >= eps
742 && fabs (my_modf (yr) - straddle) < eps)
743 dem += SECONDARY_BEAM_DEMERIT;
745 if (rdir == DOWN && dy <= eps
746 && fabs (my_modf (yr) - straddle) < eps)
747 dem += SECONDARY_BEAM_DEMERIT;
757 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
759 Beam::least_squares (SCM smob)
761 Grob *me = unsmob_grob (smob);
763 int count = visible_stem_count (me);
768 me->set_grob_property ("positions", ly_interval2scm (pos));
769 return SCM_UNSPECIFIED;
772 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
773 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
777 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
778 Stem::chord_start_y (last_visible_stem (me)));
782 TODO : use scoring for this.
784 complicated, because we take stem-info.ideal for determining
788 /* Make simple beam on middle line have small tilt */
789 if (!ideal[LEFT] && chord.delta () && count == 2)
795 Direction d = (Direction) (sign (chord.delta ()) * UP);
796 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
807 Array<Offset> ideals;
809 // ugh -> use commonx
810 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
811 Link_array<Item> stems=
812 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
814 for (int i=0; i < stems.size (); i++)
817 if (Stem::invisible_b (s))
819 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
820 Stem::calc_stem_info (s).ideal_y_));
824 minimise_least_squares (&dydx, &y, ideals);
826 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
828 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
830 pos = Interval (y, (y+dy));
833 me->set_grob_property ("positions", ly_interval2scm (pos));
834 return SCM_UNSPECIFIED;
837 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
839 Beam::check_concave (SCM smob)
841 Grob *me = unsmob_grob (smob);
843 Link_array<Item> stems =
844 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
846 for (int i = 0; i < stems.size ();)
848 if (Stem::invisible_b (stems[i]))
854 if (stems.size () < 3)
855 return SCM_UNSPECIFIED;
858 /* Concaveness #1: If distance of an inner notehead to line between
859 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
860 beam is concave (Heinz Stolba).
862 In the case of knees, the line connecting outer heads is often
863 not related to the beam slope (it may even go in the other
864 direction). Skip the check when the outer stems point in
865 different directions. --hwn
868 bool concaveness1 = false;
869 SCM gap = me->get_grob_property ("concaveness-gap");
870 if (gh_number_p (gap)
871 && Stem::get_direction(stems.top ())
872 == Stem::get_direction(stems[0]))
874 Real r1 = gh_scm2double (gap);
875 Real dy = Stem::chord_start_y (stems.top ())
876 - Stem::chord_start_y (stems[0]);
879 Real slope = dy / (stems.size () - 1);
881 Real y0 = Stem::chord_start_y (stems[0]);
882 for (int i = 1; i < stems.size () - 1; i++)
884 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
894 /* Concaveness #2: Sum distances of inner noteheads that fall
895 outside the interval of the two outer noteheads.
897 We only do this for beams where first and last stem have the same
901 Note that "convex" stems compensate for "concave" stems.
902 (is that intentional?) --hwn.
905 Real concaveness2 = 0;
906 SCM thresh = me->get_grob_property ("concaveness-threshold");
907 Real r2 = infinity_f;
908 if (!concaveness1 && gh_number_p (thresh)
909 && Stem::get_direction(stems.top ())
910 == Stem::get_direction(stems[0]))
912 r2 = gh_scm2double (thresh);
914 Direction dir = Stem::get_direction(stems.top ());
916 Interval iv (Stem::chord_start_y (stems[0]),
917 Stem::chord_start_y (stems.top ()));
919 if (iv[MAX] < iv[MIN])
922 for (int i = 1; i < stems.size () - 1; i++)
924 Real f = Stem::chord_start_y (stems[i]);
925 concave += ((f - iv[MAX] ) >? 0) +
926 ((f - iv[MIN] ) <? 0);
929 concaveness2 = concave / (stems.size () - 2);
931 /* ugh: this is the a kludge to get
932 input/regression/beam-concave.ly to behave as
936 huh? we're dividing twice (which is not scalable) meaning that
937 the longer the beam, the more unlikely it will be
938 concave. Maybe you would even expect the other way around??
943 concaveness2 /= (stems.size () - 2);
946 /* TODO: some sort of damping iso -> plain horizontal */
947 if (concaveness1 || concaveness2 > r2)
949 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
950 Real r = pos.linear_combination (0);
951 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
952 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
955 return SCM_UNSPECIFIED;
958 /* This neat trick is by Werner Lemberg,
959 damped = tanh (slope)
960 corresponds with some tables in [Wanske] CHECKME */
961 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
963 Beam::slope_damping (SCM smob)
965 Grob *me = unsmob_grob (smob);
967 if (visible_stem_count (me) <= 1)
968 return SCM_UNSPECIFIED;
970 SCM s = me->get_grob_property ("damping");
971 int damping = gh_scm2int (s);
975 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
976 Real dy = pos.delta ();
978 // ugh -> use commonx
979 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
980 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
981 Real dydx = dy && dx ? dy/dx : 0;
982 dydx = 0.6 * tanh (dydx) / damping;
984 Real damped_dy = dydx * dx;
985 pos[LEFT] += (dy - damped_dy) / 2;
986 pos[RIGHT] -= (dy - damped_dy) / 2;
988 me->set_grob_property ("positions", ly_interval2scm (pos));
990 return SCM_UNSPECIFIED;
994 Calculate the Y position of the stem-end, given the Y-left, Y-right
995 in POS, and for stem S.
997 If CORRECT, correct for multiplicity of beam in case of knees.
1000 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool correct)
1002 int beam_multiplicity = get_multiplicity (me);
1003 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
1005 int first_multiplicity = (Stem::duration_log (first_visible_stem (me))
1007 int last_multiplicity = (Stem::duration_log (last_visible_stem (me))
1010 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1011 Real interbeam = get_interbeam (me);
1013 // ugh -> use commonx
1014 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1015 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1016 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1017 Real dy = pos.delta ();
1018 Real stem_y = (dy && dx
1023 Direction first_dir = Directional_element_interface::get (first_visible_stem (me));
1024 Direction my_dir = Directional_element_interface::get (s);
1026 if (correct && my_dir != first_dir)
1029 WTF is happening here ?
1031 It looks as if this is some kind of fixup for multiple kneed
1032 beams to get a piece of stem at the #.
1043 Rules for this kind of stuff are hairy. In any event, the
1044 current stem should look at the multiplicity of its
1051 // FIXME, hairy stuff
1052 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1054 // huh, why not for first visible?
1057 What the heck is happening here??
1059 Grob *last_visible = last_visible_stem (me);
1062 if ( Staff_symbol_referencer::staff_symbol_l (s)
1063 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1064 stem_y += Directional_element_interface::get (me)
1065 * (beam_multiplicity - stem_multiplicity) * interbeam;
1068 programming_error ("No last visible stem");
1074 Hmm. At this time, beam position and slope are determined. Maybe,
1075 stem directions and length should set to relative to the chord's
1076 position of the beam. */
1078 Beam::set_stem_lengths (Grob *me)
1080 Link_array<Item> stems=
1081 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1083 if (stems.size () <= 1)
1086 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1087 for (int i=1; i < stems.size (); i++)
1088 if (!Stem::invisible_b (stems[i]))
1089 common = common->common_refpoint (stems[i], Y_AXIS);
1091 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1092 Real staff_space = Staff_symbol_referencer::staff_space (me);
1098 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1099 Direction dir = Directional_element_interface::get (me);
1100 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1103 for (int i=0; i < stems.size (); i++)
1106 if (Stem::invisible_b (s))
1109 Real stem_y = calc_stem_y (me, s, pos, true);
1112 // doesn't play well with dvips
1114 if (Stem::get_direction (s) == dir)
1115 stem_y += Stem::get_direction (s) * thick / 2;
1118 /* caution: stem measures in staff-positions */
1119 Real id = me->relative_coordinate (common, Y_AXIS)
1120 - stems[i]->relative_coordinate (common, Y_AXIS);
1121 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1126 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1128 Link_array<Grob> stems=
1129 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1132 for (int i=0; i < stems.size (); i++)
1136 /* Don't overwrite user override (?) */
1137 if (Stem::beam_count (stems[i], d) == -1
1138 /* Don't set beaming for outside of outer stems */
1139 && ! (d == LEFT && i == 0)
1140 && ! (d == RIGHT && i == stems.size () -1))
1142 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1143 Stem::set_beaming (stems[i], b, d);
1146 while (flip (&d) != LEFT);
1153 beams to go with one stem.
1157 The beam should be constructed by one function that knows where the
1158 X and Y points are, and only inspects the stems to obtain
1159 multiplicity and stem directions.
1163 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1165 // ugh -> use commonx
1167 && ! (next->relative_coordinate (0, X_AXIS)
1168 > here->relative_coordinate (0, X_AXIS)))
1170 && ! (prev->relative_coordinate (0, X_AXIS)
1171 < here->relative_coordinate (0, X_AXIS))))
1172 programming_error ("Beams are not left-to-right");
1174 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1175 Real bdy = get_interbeam (me);
1178 Molecule rightbeams;
1181 if (!Stem::first_head (here))
1185 int t = Stem::duration_log (here);
1187 SCM proc = me->get_grob_property ("flag-width-function");
1188 SCM result = gh_call1 (proc, gh_int2scm (t));
1189 nw_f = gh_scm2double (result);
1193 /* [Tremolo] beams on whole notes may not have direction set? */
1194 Direction dir = Directional_element_interface::get (here);
1196 /* half beams extending to the left. */
1199 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1200 - Stem::beam_count (prev, RIGHT);
1201 int lwholebeams= Stem::beam_count (here, LEFT)
1202 <? Stem::beam_count (prev, RIGHT);
1204 /* Half beam should be one note-width,
1205 but let's make sure two half-beams never touch */
1207 // FIXME: TODO (check) stem width / sloped beams
1208 Real w = here->relative_coordinate (0, X_AXIS)
1209 - prev->relative_coordinate (0, X_AXIS);
1210 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1212 * me->paper_l ()->get_var ("linethickness");
1216 if (lhalfs) // generates warnings if not
1217 a = Lookup::beam (dydx, w + stem_w, thick);
1218 a.translate (Offset (-w, -w * dydx));
1219 a.translate_axis (-stem_w/2, X_AXIS);
1220 for (int j = 0; j < lhalfs; j++)
1223 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1224 leftbeams.add_molecule (b);
1230 int rhalfs = Stem::beam_count (here, RIGHT)
1231 - Stem::beam_count (next, LEFT);
1232 int rwholebeams= Stem::beam_count (here, RIGHT)
1233 <? Stem::beam_count (next, LEFT);
1235 Real w = next->relative_coordinate (0, X_AXIS)
1236 - here->relative_coordinate (0, X_AXIS);
1238 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1240 * me->paper_l ()->get_var ("linethickness");
1242 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1243 a.translate_axis (- stem_w/2, X_AXIS);
1247 SCM gap = me->get_grob_property ("gap");
1248 if (gh_number_p (gap))
1250 int gap_i = gh_scm2int ((gap));
1251 int nogap = rwholebeams - gap_i;
1253 for (; j < nogap; j++)
1256 b.translate_axis (-dir * bdy * j, Y_AXIS);
1257 rightbeams.add_molecule (b);
1259 if (Stem::invisible_b (here))
1264 a = Lookup::beam (dydx, w + stem_w, thick);
1267 for (; j < rwholebeams; j++)
1271 if (Stem::invisible_b (here))
1272 // ugh, see chord-tremolo.ly
1273 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1276 b.translate (Offset (tx, -dir * bdy * j));
1277 rightbeams.add_molecule (b);
1282 a = Lookup::beam (dydx, w, thick);
1284 for (; j < rwholebeams + rhalfs; j++)
1287 b.translate_axis (- dir * bdy * j, Y_AXIS);
1288 rightbeams.add_molecule (b);
1292 leftbeams.add_molecule (rightbeams);
1298 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1300 Beam::brew_molecule (SCM smob)
1302 Grob *me =unsmob_grob (smob);
1305 if (!gh_pair_p (me->get_grob_property ("stems")))
1308 Link_array<Item>stems =
1309 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1310 if (visible_stem_count (me))
1312 // ugh -> use commonx
1313 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1314 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1318 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1319 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1322 SCM posns = me->get_grob_property ("positions");
1324 if (!ly_number_pair_p (posns))
1326 programming_error ("No beam posns");
1327 pos = Interval (0,0);
1330 pos= ly_scm2interval (posns);
1331 Real dy = pos.delta ();
1332 Real dydx = dy && dx ? dy/dx : 0;
1335 Direction firstdir = Directional_element_interface::get ( Beam::first_visible_stem (me) );
1337 for (int i=0; i < stems.size (); i++)
1339 Item *item = stems[i];
1340 Item *prev = (i > 0)? stems[i-1] : 0;
1341 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1345 Molecule sb = stem_beams (me, item, next, prev, dydx);
1346 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1347 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1349 Direction sd = Stem::get_direction (item);
1350 mol.add_molecule (sb);
1353 mol.translate_axis (x0
1354 - dynamic_cast<Spanner*> (me)
1355 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1358 #if (DEBUG_QUANTING)
1361 This code prints the demerits for each beam. Perhaps this
1362 should be switchable for those who want to twiddle with the
1368 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1371 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1374 SCM properties = Font_interface::font_alist_chain (me);
1377 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1378 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1382 return mol.smobbed_copy ();
1386 Beam::forced_stem_count (Grob *me)
1388 Link_array<Item>stems =
1389 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1391 for (int i=0; i < stems.size (); i++)
1395 if (Stem::invisible_b (s))
1398 if (((int)Stem::chord_start_y (s))
1399 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1409 Beam::visible_stem_count (Grob *me)
1411 Link_array<Item>stems =
1412 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1414 for (int i = stems.size (); i--;)
1416 if (!Stem::invisible_b (stems[i]))
1423 Beam::first_visible_stem (Grob *me)
1425 Link_array<Item>stems =
1426 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1428 for (int i = 0; i < stems.size (); i++)
1430 if (!Stem::invisible_b (stems[i]))
1437 Beam::last_visible_stem (Grob *me)
1439 Link_array<Item>stems =
1440 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1441 for (int i = stems.size (); i--;)
1443 if (!Stem::invisible_b (stems[i]))
1453 handle rest under beam (do_post: beams are calculated now)
1454 what about combination of collisions and rest under beam.
1458 rest -> stem -> beam -> interpolate_y_position ()
1460 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1462 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1464 Grob *rest = unsmob_grob (element_smob);
1465 Axis a = (Axis) gh_scm2int (axis);
1467 assert (a == Y_AXIS);
1469 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1472 return gh_double2scm (0.0);
1473 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1475 || !Beam::has_interface (beam)
1476 || !Beam::visible_stem_count (beam))
1477 return gh_double2scm (0.0);
1479 // make callback for rest from this.
1480 // todo: make sure this calced already.
1482 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1483 Interval pos (0, 0);
1484 SCM s = beam->get_grob_property ("positions");
1485 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1486 pos = ly_scm2interval (s);
1488 Real dy = pos.delta ();
1489 // ugh -> use commonx
1490 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1491 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1492 Real dydx = dy && dx ? dy/dx : 0;
1494 Direction d = Stem::get_direction (stem);
1495 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1497 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1500 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1503 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1505 minimum_dist + -d * (beamy - rest_dim) >? 0;
1507 int stafflines = Staff_symbol_referencer::line_count (rest);
1509 // move discretely by half spaces.
1510 int discrete_dist = int (ceil (dist));
1512 // move by whole spaces inside the staff.
1513 if (discrete_dist < stafflines+1)
1514 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1516 return gh_double2scm (-d * discrete_dist);
1522 ADD_INTERFACE (Beam, "beam-interface",
1525 #'thickness= weight of beams, in staffspace
1528 We take the least squares line through the ideal-length stems, and
1529 then damp that using
1531 damped = tanh (slope)
1533 this gives an unquantized left and right position for the beam end.
1534 Then we take all combinations of quantings near these left and right
1535 positions, and give them a score (according to how close they are to
1536 the ideal slope, how close the result is to the ideal stems, etc.). We
1537 take the best scoring combination.
1540 "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");