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).
30 #include <math.h> // tanh.
32 #include "molecule.hh"
33 #include "directional-element-interface.hh"
37 #include "least-squares.hh"
39 #include "paper-def.hh"
41 #include "group-interface.hh"
42 #include "staff-symbol-referencer.hh"
48 #define DEBUG_QUANTING 0
52 #include "text-item.hh" // debug output.
53 #include "font-interface.hh" // debug output.
57 const int INTER_QUANT_PENALTY = 1000;
58 const int SECONDARY_BEAM_DEMERIT = 15;
59 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
60 // possibly ridiculous, but too short stems just won't do
61 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
62 const int DAMPING_DIRECTIION_PENALTY = 800;
63 const int MUSICAL_DIRECTION_FACTOR = 400;
64 const int IDEAL_SLOPE_FACTOR = 10;
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
350 grob-properties y, dy.
352 User may set grob-properties: y-position-hs and height-hs
353 (to be fixed) that override the calculated y and dy.
355 Because y and dy cannot be calculated and quanted separately, we
356 always calculate both, then check for user override. */
357 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
359 Beam::after_line_breaking (SCM smob)
361 Grob *me = unsmob_grob (smob);
363 /* Copy to mutable list. */
364 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
365 me->set_grob_property ("positions", s);
367 if (ly_car (s) != SCM_BOOL_F)
368 return SCM_UNSPECIFIED;
370 // one wonders if such genericity is necessary --hwn.
371 SCM callbacks = me->get_grob_property ("position-callbacks");
372 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
373 gh_call1 (ly_car (i), smob);
375 set_stem_lengths (me);
376 return SCM_UNSPECIFIED;
390 - Make all demerits customisable
392 - One sensible check per demerit (what's this --hwn)
394 - Add demerits for quants per se, as to forbid a specific quant
398 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
400 Beam::quanting (SCM smob)
402 Grob *me = unsmob_grob (smob);
404 SCM s = me->get_grob_property ("positions");
405 Real yl = gh_scm2double (gh_car (s));
406 Real yr = gh_scm2double (gh_cdr (s));
408 Real ss = Staff_symbol_referencer::staff_space (me);
409 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
410 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
413 SCM sdy = me->get_grob_property ("least-squares-dy");
414 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
417 Real sit = (thickness - slt) / 2;
419 Real hang = 1.0 - (thickness - slt) / 2;
420 Real quants [] = {straddle, sit, inter, hang };
422 int num_quants = int (sizeof (quants)/sizeof (Real));
427 going to REGION_SIZE == 2, yields another 0.6 second with
431 (result indexes between 70 and 575) ? --hwn.
438 Do stem computations. These depend on YL and YR linearly, so we can
439 precompute for every stem 2 factors.
441 Link_array<Grob> stems=
442 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
443 Array<Stem_info> stem_infos;
444 Array<Real> lbase_lengths;
445 Array<Real> rbase_lengths;
447 Drul_array<bool> dirs_found(0,0);
448 for (int i= 0; i < stems.size(); i++)
451 stem_infos.push (Stem::calc_stem_info (s));
452 dirs_found[stem_infos.top ().dir_] = true;
455 Real b = calc_stem_y (me, s, Interval (1,0));
456 lbase_lengths.push (b);
458 Real a = calc_stem_y (me, s, Interval (0,1));
459 rbase_lengths.push (a);
463 Direction ldir = Direction (stem_infos[0].dir_);
464 Direction rdir = Direction (stem_infos.top ().dir_);
465 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
471 Knees are harder, lets try some more possibilities for knees.
476 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
477 for (int j = 0; j < num_quants; j++)
479 quantsl.push (i + quants[j] + int (yl));
480 quantsr.push (i + quants[j] + int (yr));
483 Array<Quant_score> qscores;
485 for (int l =0; l < quantsl.size (); l++)
486 for (int r =0; r < quantsr.size (); r++)
498 This is a longish function, but we don't separate this out into
499 neat modular separate subfunctions, as the subfunctions would be
500 called for many values of YL, YR. By precomputing various
501 parameters outside of the loop, we can save a lot of time.
504 for (int i = qscores.size (); i--;)
505 if (qscores[i].demerits < 100)
508 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
512 Real rad = Staff_symbol_referencer::staff_radius (me);
513 int multiplicity = get_multiplicity (me);
514 Real interbeam = multiplicity < 4
515 ? (2*ss + slt - thickness) / 2.0
516 : (3*ss + slt - thickness) / 3.0;
518 for (int i = qscores.size (); i--;)
519 if (qscores[i].demerits < 100)
522 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
523 rad, slt, thickness, interbeam,
524 multiplicity, ldir, rdir);
528 for (int i = qscores.size (); i--;)
529 if (qscores[i].demerits < 100)
532 += score_stem_lengths (stems, stem_infos,
533 lbase_lengths, rbase_lengths,
535 me, qscores[i].yl, qscores[i].yr);
541 for (int i = qscores.size (); i--;)
543 if (qscores[i].demerits < best)
545 best = qscores [i].demerits ;
551 me->set_grob_property ("positions",
552 gh_cons (gh_double2scm (qscores[best_idx].yl),
553 gh_double2scm (qscores[best_idx].yr))
559 me->set_grob_property ("quant-score",
560 gh_double2scm (qscores[best_idx].demerits));
561 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
564 return SCM_UNSPECIFIED;
568 Beam::score_stem_lengths (Link_array<Grob>stems,
569 Array<Stem_info> stem_infos,
570 Array<Real> left_factor,
571 Array<Real> right_factor,
576 Real demerit_score = 0.0 ;
577 Real pen = STEM_LENGTH_LIMIT_PENALTY;
584 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
585 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
587 for (int i=0; i < stems.size (); i++)
590 if (Stem::invisible_b (s))
594 /* for a two-stemmed, interstaff beam knee up/down:
597 \context PianoStaff \notes\relative c' <
598 \context Staff = lh {
599 \stemDown [c8 \translator Staff = rh \stemUp a'' ]
601 \context Staff = rh \relative c' s4
605 with yl = -5.8 (about ideal)
606 and yr = -1 (ridiculous pos)
607 this yields current_y = -8.1 (about ideal) */
610 yl * left_factor[i] + right_factor[i]* yr;
612 Real f = (s->relative_coordinate (0, X_AXIS) - x0) / dx;
613 Real current_y = yl + f * (yr - yl);
616 Stem_info info = stem_infos[i];
617 Direction d = info.dir_;
620 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
622 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
623 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
626 demerit_score *= 2.0 / stems.size ();
628 return demerit_score;
632 Beam::score_slopes_dy (Grob *me,
634 Real dy_mus, Real dy_damp)
639 if (sign (dy_damp) != sign (dy))
641 dem += DAMPING_DIRECTIION_PENALTY;
644 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
645 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
653 return x - floor (x);
657 Beam::score_forbidden_quants (Grob*me,
661 Real thickness, Real interbeam,
663 Direction ldir, Direction rdir)
668 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
669 dem += INTER_QUANT_PENALTY;
670 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
671 dem += INTER_QUANT_PENALTY;
673 // todo: use multiplicity of outer stems.
674 if (multiplicity >= 2)
678 Real sit = (thickness - slt) / 2;
680 Real hang = 1.0 - (thickness - slt) / 2;
683 if (fabs (yl - ldir * interbeam) < rad
684 && fabs (my_modf (yl) - inter) < 1e-3)
685 dem += SECONDARY_BEAM_DEMERIT;
686 if (fabs (yr - rdir * interbeam) < rad
687 && fabs (my_modf (yr) - inter) < 1e-3)
688 dem += SECONDARY_BEAM_DEMERIT;
693 Can't we simply compute the distance between the nearest
694 staffline and the secondary beam? That would get rid of the
695 silly case analysis here (which is probably not when we have
696 different beam-thicknesses.)
702 // hmm, without Interval/Drul_array, you get ~ 4x same code...
703 if (fabs (yl - ldir * interbeam) < rad + inter)
705 if (ldir == UP && dy <= eps
706 && fabs (my_modf (yl) - sit) < eps)
707 dem += SECONDARY_BEAM_DEMERIT;
709 if (ldir == DOWN && dy >= eps
710 && fabs (my_modf (yl) - hang) < eps)
711 dem += SECONDARY_BEAM_DEMERIT;
714 if (fabs (yr - rdir * interbeam) < rad + inter)
716 if (rdir == UP && dy >= eps
717 && fabs (my_modf (yr) - sit) < eps)
718 dem += SECONDARY_BEAM_DEMERIT;
720 if (rdir == DOWN && dy <= eps
721 && fabs (my_modf (yr) - hang) < eps)
722 dem += SECONDARY_BEAM_DEMERIT;
725 if (multiplicity >= 3)
727 if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
729 if (ldir == UP && dy <= eps
730 && fabs (my_modf (yl) - straddle) < eps)
731 dem += SECONDARY_BEAM_DEMERIT;
733 if (ldir == DOWN && dy >= eps
734 && fabs (my_modf (yl) - straddle) < eps)
735 dem += SECONDARY_BEAM_DEMERIT;
738 if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
740 if (rdir == UP && dy >= eps
741 && fabs (my_modf (yr) - straddle) < eps)
742 dem += SECONDARY_BEAM_DEMERIT;
744 if (rdir == DOWN && dy <= eps
745 && fabs (my_modf (yr) - straddle) < eps)
746 dem += SECONDARY_BEAM_DEMERIT;
756 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
758 Beam::least_squares (SCM smob)
760 Grob *me = unsmob_grob (smob);
762 int count = visible_stem_count (me);
767 me->set_grob_property ("positions", ly_interval2scm (pos));
768 return SCM_UNSPECIFIED;
771 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
772 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
776 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
777 Stem::chord_start_y (last_visible_stem (me)));
781 TODO : use scoring for this.
783 complicated, because we take stem-info.ideal for determining
787 /* Make simple beam on middle line have small tilt */
788 if (!ideal[LEFT] && chord.delta () && count == 2)
794 Direction d = (Direction) (sign (chord.delta ()) * UP);
795 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
806 Array<Offset> ideals;
808 // ugh -> use commonx
809 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
810 Link_array<Item> stems=
811 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
813 for (int i=0; i < stems.size (); i++)
816 if (Stem::invisible_b (s))
818 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
819 Stem::calc_stem_info (s).ideal_y_));
823 minimise_least_squares (&dydx, &y, ideals);
825 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
827 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
829 pos = Interval (y, (y+dy));
832 me->set_grob_property ("positions", ly_interval2scm (pos));
833 return SCM_UNSPECIFIED;
836 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
838 Beam::check_concave (SCM smob)
840 Grob *me = unsmob_grob (smob);
842 Link_array<Item> stems =
843 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
845 for (int i = 0; i < stems.size ();)
847 if (Stem::invisible_b (stems[i]))
853 if (stems.size () < 3)
854 return SCM_UNSPECIFIED;
857 /* Concaveness #1: If distance of an inner notehead to line between
858 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
859 beam is concave (Heinz Stolba).
861 In the case of knees, the line connecting outer heads is often
862 not related to the beam slope (it may even go in the other
863 direction). Skip the check when the outer stems point in
864 different directions. --hwn
867 bool concaveness1 = false;
868 SCM gap = me->get_grob_property ("concaveness-gap");
869 if (gh_number_p (gap)
870 && Stem::get_direction(stems.top ())
871 == Stem::get_direction(stems[0]))
873 Real r1 = gh_scm2double (gap);
874 Real dy = Stem::chord_start_y (stems.top ())
875 - Stem::chord_start_y (stems[0]);
878 Real slope = dy / (stems.size () - 1);
880 Real y0 = Stem::chord_start_y (stems[0]);
881 for (int i = 1; i < stems.size () - 1; i++)
883 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
893 /* Concaveness #2: Sum distances of inner noteheads that fall
894 outside the interval of the two outer noteheads.
896 We only do this for beams where first and last stem have the same
900 Note that "convex" stems compensate for "concave" stems.
901 (is that intentional?) --hwn.
904 Real concaveness2 = 0;
905 SCM thresh = me->get_grob_property ("concaveness-threshold");
906 Real r2 = infinity_f;
907 if (!concaveness1 && gh_number_p (thresh)
908 && Stem::get_direction(stems.top ())
909 == Stem::get_direction(stems[0]))
911 r2 = gh_scm2double (thresh);
913 Direction dir = Stem::get_direction(stems.top ());
915 Interval iv (Stem::chord_start_y (stems[0]),
916 Stem::chord_start_y (stems.top ()));
918 if (iv[MAX] < iv[MIN])
921 for (int i = 1; i < stems.size () - 1; i++)
923 Real f = Stem::chord_start_y (stems[i]);
924 concave += ((f - iv[MAX] ) >? 0) +
925 ((f - iv[MIN] ) <? 0);
928 concaveness2 = concave / (stems.size () - 2);
930 /* ugh: this is the a kludge to get
931 input/regression/beam-concave.ly to behave as
935 huh? we're dividing twice (which is not scalable) meaning that
936 the longer the beam, the more unlikely it will be
937 concave. Maybe you would even expect the other way around??
942 concaveness2 /= (stems.size () - 2);
945 /* TODO: some sort of damping iso -> plain horizontal */
946 if (concaveness1 || concaveness2 > r2)
948 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
949 Real r = pos.linear_combination (0);
950 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
951 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
954 return SCM_UNSPECIFIED;
957 /* This neat trick is by Werner Lemberg,
958 damped = tanh (slope)
959 corresponds with some tables in [Wanske] CHECKME */
960 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
962 Beam::slope_damping (SCM smob)
964 Grob *me = unsmob_grob (smob);
966 if (visible_stem_count (me) <= 1)
967 return SCM_UNSPECIFIED;
969 SCM s = me->get_grob_property ("damping");
970 int damping = gh_scm2int (s);
974 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
975 Real dy = pos.delta ();
977 // ugh -> use commonx
978 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
979 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
980 Real dydx = dy && dx ? dy/dx : 0;
981 dydx = 0.6 * tanh (dydx) / damping;
983 Real damped_dy = dydx * dx;
984 pos[LEFT] += (dy - damped_dy) / 2;
985 pos[RIGHT] -= (dy - damped_dy) / 2;
987 me->set_grob_property ("positions", ly_interval2scm (pos));
989 return SCM_UNSPECIFIED;
993 Calculate the Y position of the stem-end, given the Y-left, Y-right
994 in POS, and for stem S.
997 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
999 int beam_multiplicity = get_multiplicity (me);
1000 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
1002 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1003 Real interbeam = get_interbeam (me);
1005 // ugh -> use commonx
1006 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1007 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1008 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1009 Real dy = pos.delta ();
1010 Real stem_y = (dy && dx
1011 ? r / dx //(s->relative_coordinate (0, X_AXIS) - x0) / dx
1015 Direction first_dir = Directional_element_interface::get (first_visible_stem (me));
1016 Direction my_dir = Directional_element_interface::get (s);
1018 if (my_dir != first_dir)
1021 WTF is happening here ?
1023 It looks as if this is some kind of fixup for multiple kneed
1024 beams to get a piece of stem at the #.
1035 Rules for this kind of stuff are hairy. In any event, the
1036 current stem should look at the multiplicity of its
1042 stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1044 // huh, why not for first visible?
1047 What the heck is happening here??
1049 Grob *last_visible = last_visible_stem (me);
1052 if ( Staff_symbol_referencer::staff_symbol_l (s)
1053 != Staff_symbol_referencer::staff_symbol_l (last_visible))
1054 stem_y += Directional_element_interface::get (me)
1055 * (beam_multiplicity - stem_multiplicity) * interbeam;
1058 programming_error ("No last visible stem");
1065 Hmm. At this time, beam position and slope are determined. Maybe,
1066 stem directions and length should set to relative to the chord's
1067 position of the beam. */
1069 Beam::set_stem_lengths (Grob *me)
1071 Link_array<Item> stems=
1072 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1074 if (stems.size () <= 1)
1077 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1078 for (int i=1; i < stems.size (); i++)
1079 if (!Stem::invisible_b (stems[i]))
1080 common = common->common_refpoint (stems[i], Y_AXIS);
1082 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1083 Real staff_space = Staff_symbol_referencer::staff_space (me);
1089 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1090 Direction dir = Directional_element_interface::get (me);
1091 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1094 for (int i=0; i < stems.size (); i++)
1097 if (Stem::invisible_b (s))
1100 Real stem_y = calc_stem_y (me, s, pos);
1103 // doesn't play well with dvips
1105 if (Stem::get_direction (s) == dir)
1106 stem_y += Stem::get_direction (s) * thick / 2;
1109 /* caution: stem measures in staff-positions */
1110 Real id = me->relative_coordinate (common, Y_AXIS)
1111 - stems[i]->relative_coordinate (common, Y_AXIS);
1112 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1117 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1119 Link_array<Grob> stems=
1120 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1123 for (int i=0; i < stems.size (); i++)
1127 /* Don't overwrite user override (?) */
1128 if (Stem::beam_count (stems[i], d) == -1
1129 /* Don't set beaming for outside of outer stems */
1130 && ! (d == LEFT && i == 0)
1131 && ! (d == RIGHT && i == stems.size () -1))
1133 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1134 Stem::set_beaming (stems[i], b, d);
1137 while (flip (&d) != LEFT);
1144 beams to go with one stem.
1148 The beam should be constructed by one function that knows where the
1149 X and Y points are, and only inspects the stems to obtain
1150 multiplicity and stem directions.
1154 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1156 // ugh -> use commonx
1158 && ! (next->relative_coordinate (0, X_AXIS)
1159 > here->relative_coordinate (0, X_AXIS)))
1161 && ! (prev->relative_coordinate (0, X_AXIS)
1162 < here->relative_coordinate (0, X_AXIS))))
1163 programming_error ("Beams are not left-to-right");
1165 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1166 Real bdy = get_interbeam (me);
1169 Molecule rightbeams;
1172 if (!Stem::first_head (here))
1176 int t = Stem::duration_log (here);
1178 SCM proc = me->get_grob_property ("flag-width-function");
1179 SCM result = gh_call1 (proc, gh_int2scm (t));
1180 nw_f = gh_scm2double (result);
1184 /* [Tremolo] beams on whole notes may not have direction set? */
1185 Direction dir = Directional_element_interface::get (here);
1187 /* half beams extending to the left. */
1190 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1191 - Stem::beam_count (prev, RIGHT);
1192 int lwholebeams= Stem::beam_count (here, LEFT)
1193 <? Stem::beam_count (prev, RIGHT);
1195 /* Half beam should be one note-width,
1196 but let's make sure two half-beams never touch */
1198 // FIXME: TODO (check) stem width / sloped beams
1199 Real w = here->relative_coordinate (0, X_AXIS)
1200 - prev->relative_coordinate (0, X_AXIS);
1201 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1203 * me->paper_l ()->get_var ("linethickness");
1207 if (lhalfs) // generates warnings if not
1208 a = Lookup::beam (dydx, w + stem_w, thick);
1209 a.translate (Offset (-w, -w * dydx));
1210 a.translate_axis (-stem_w/2, X_AXIS);
1211 for (int j = 0; j < lhalfs; j++)
1214 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1215 leftbeams.add_molecule (b);
1221 int rhalfs = Stem::beam_count (here, RIGHT)
1222 - Stem::beam_count (next, LEFT);
1223 int rwholebeams= Stem::beam_count (here, RIGHT)
1224 <? Stem::beam_count (next, LEFT);
1226 Real w = next->relative_coordinate (0, X_AXIS)
1227 - here->relative_coordinate (0, X_AXIS);
1229 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1231 * me->paper_l ()->get_var ("linethickness");
1233 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1234 a.translate_axis (- stem_w/2, X_AXIS);
1238 SCM gap = me->get_grob_property ("gap");
1239 if (gh_number_p (gap))
1241 int gap_i = gh_scm2int ((gap));
1242 int nogap = rwholebeams - gap_i;
1244 for (; j < nogap; j++)
1247 b.translate_axis (-dir * bdy * j, Y_AXIS);
1248 rightbeams.add_molecule (b);
1250 if (Stem::invisible_b (here))
1255 a = Lookup::beam (dydx, w + stem_w, thick);
1258 for (; j < rwholebeams; j++)
1262 if (Stem::invisible_b (here))
1263 // ugh, see chord-tremolo.ly
1264 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1267 b.translate (Offset (tx, -dir * bdy * j));
1268 rightbeams.add_molecule (b);
1273 a = Lookup::beam (dydx, w, thick);
1275 for (; j < rwholebeams + rhalfs; j++)
1278 b.translate_axis (- dir * bdy * j, Y_AXIS);
1279 rightbeams.add_molecule (b);
1283 leftbeams.add_molecule (rightbeams);
1289 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1291 Beam::brew_molecule (SCM smob)
1293 Grob *me =unsmob_grob (smob);
1296 if (!gh_pair_p (me->get_grob_property ("stems")))
1299 Link_array<Item>stems =
1300 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1301 if (visible_stem_count (me))
1303 // ugh -> use commonx
1304 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1305 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1309 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1310 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1313 SCM posns = me->get_grob_property ("positions");
1315 if (!ly_number_pair_p (posns))
1317 programming_error ("No beam posns");
1318 pos = Interval (0,0);
1321 pos= ly_scm2interval (posns);
1322 Real dy = pos.delta ();
1323 Real dydx = dy && dx ? dy/dx : 0;
1326 Direction firstdir = Directional_element_interface::get ( Beam::first_visible_stem (me) );
1328 for (int i=0; i < stems.size (); i++)
1330 Item *item = stems[i];
1331 Item *prev = (i > 0)? stems[i-1] : 0;
1332 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1336 Molecule sb = stem_beams (me, item, next, prev, dydx);
1337 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1338 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1340 Direction sd = Stem::get_direction (item);
1341 mol.add_molecule (sb);
1344 mol.translate_axis (x0
1345 - dynamic_cast<Spanner*> (me)
1346 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1349 #if (DEBUG_QUANTING)
1352 This code prints the demerits for each beam. Perhaps this
1353 should be switchable for those who want to twiddle with the
1359 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1362 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1365 SCM properties = Font_interface::font_alist_chain (me);
1368 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1369 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1373 return mol.smobbed_copy ();
1377 Beam::forced_stem_count (Grob *me)
1379 Link_array<Item>stems =
1380 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1382 for (int i=0; i < stems.size (); i++)
1386 if (Stem::invisible_b (s))
1389 if (((int)Stem::chord_start_y (s))
1390 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1400 Beam::visible_stem_count (Grob *me)
1402 Link_array<Item>stems =
1403 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1405 for (int i = stems.size (); i--;)
1407 if (!Stem::invisible_b (stems[i]))
1414 Beam::first_visible_stem (Grob *me)
1416 Link_array<Item>stems =
1417 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1419 for (int i = 0; i < stems.size (); i++)
1421 if (!Stem::invisible_b (stems[i]))
1428 Beam::last_visible_stem (Grob *me)
1430 Link_array<Item>stems =
1431 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1432 for (int i = stems.size (); i--;)
1434 if (!Stem::invisible_b (stems[i]))
1444 handle rest under beam (do_post: beams are calculated now)
1445 what about combination of collisions and rest under beam.
1449 rest -> stem -> beam -> interpolate_y_position ()
1451 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1453 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1455 Grob *rest = unsmob_grob (element_smob);
1456 Axis a = (Axis) gh_scm2int (axis);
1458 assert (a == Y_AXIS);
1460 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1463 return gh_double2scm (0.0);
1464 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1466 || !Beam::has_interface (beam)
1467 || !Beam::visible_stem_count (beam))
1468 return gh_double2scm (0.0);
1470 // make callback for rest from this.
1471 // todo: make sure this calced already.
1473 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1474 Interval pos (0, 0);
1475 SCM s = beam->get_grob_property ("positions");
1476 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1477 pos = ly_scm2interval (s);
1479 Real dy = pos.delta ();
1480 // ugh -> use commonx
1481 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1482 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1483 Real dydx = dy && dx ? dy/dx : 0;
1485 Direction d = Stem::get_direction (stem);
1486 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1488 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1491 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1494 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1496 minimum_dist + -d * (beamy - rest_dim) >? 0;
1498 int stafflines = Staff_symbol_referencer::line_count (rest);
1500 // move discretely by half spaces.
1501 int discrete_dist = int (ceil (dist));
1503 // move by whole spaces inside the staff.
1504 if (discrete_dist < stafflines+1)
1505 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1507 return gh_double2scm (-d * discrete_dist);
1513 ADD_INTERFACE (Beam, "beam-interface",
1516 #'thickness= weight of beams, in staffspace
1519 We take the least squares line through the ideal-length stems, and
1520 then damp that using
1522 damped = tanh (slope)
1524 this gives an unquantized left and right position for the beam end.
1525 Then we take all combinations of quantings near these left and right
1526 positions, and give them a score (according to how close they are to
1527 the ideal slope, how close the result is to the ideal stems, etc.). We
1528 take the best scoring combination.
1531 "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");