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"
46 #include "text-item.hh" // debug output.
47 #include "font-interface.hh" // debug output.
50 #define DEBUG_QUANTING 0
52 const int INTER_QUANT_PENALTY = 1000;
53 const int SECONDARY_BEAM_DEMERIT = 15;
54 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
55 const int STEM_LENGTH_LIMIT_PENALTY = 500;
56 const int DAMPING_DIRECTIION_PENALTY = 800;
57 const int MUSICAL_DIRECTION_FACTOR = 400;
58 const int IDEAL_SLOPE_FACTOR = 10;
62 shrink_extra_weight (Real x)
64 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
68 Beam::add_stem (Grob *me, Grob *s)
70 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
72 s->add_dependency (me);
74 assert (!Stem::beam_l (s));
75 s->set_grob_property ("beam", me->self_scm ());
77 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
81 Beam::get_interbeam (Grob *me)
83 SCM func = me->get_grob_property ("space-function");
84 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
85 return gh_scm2double (s);
89 Beam::get_multiplicity (Grob *me)
92 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
94 Grob *sc = unsmob_grob (ly_car (s));
96 if (Stem::has_interface (sc))
97 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
102 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
104 Beam::space_function (SCM smob, SCM multiplicity)
106 Grob *me = unsmob_grob (smob);
108 Real staff_space = Staff_symbol_referencer::staff_space (me);
109 Real line = me->paper_l ()->get_var ("linethickness");
110 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
113 Real interbeam = gh_scm2int (multiplicity) < 4
114 ? (2*staff_space + line - thickness) / 2.0
115 : (3*staff_space + line - thickness) / 3.0;
117 return gh_double2scm (interbeam);
121 /* After pre-processing all directions should be set.
122 Several post-processing routines (stem, slur, script) need stem/beam
124 Currenly, this means that beam has set all stem's directions.
125 [Alternatively, stems could set its own directions, according to
126 their beam, during 'final-pre-processing'.] */
127 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
129 Beam::before_line_breaking (SCM smob)
131 Grob *me = unsmob_grob (smob);
133 /* Beams with less than 2 two stems don't make much sense, but could happen
138 For a beam that only has one stem, we try to do some disappearance magic:
139 we revert the flag, and move on to The Eternal Engraving Fields. */
141 int count = visible_stem_count (me);
144 me->warning (_ ("beam has less than two visible stems"));
146 SCM stems = me->get_grob_property ("stems");
147 if (scm_ilength (stems) == 1)
149 me->warning (_ ("Beam has less than two stems. Removing beam."));
151 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
154 return SCM_UNSPECIFIED;
156 else if (scm_ilength (stems) == 0)
159 return SCM_UNSPECIFIED;
164 if (!Directional_element_interface::get (me))
165 Directional_element_interface::set (me, get_default_dir (me));
167 consider_auto_knees (me);
168 set_stem_directions (me);
169 set_stem_shorten (me);
175 Beam::get_default_dir (Grob *me)
177 Drul_array<int> total;
178 total[UP] = total[DOWN] = 0;
179 Drul_array<int> count;
180 count[UP] = count[DOWN] = 0;
183 Link_array<Item> stems=
184 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
186 for (int i=0; i <stems.size (); i++)
189 Direction sd = Directional_element_interface::get (s);
191 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
192 int current = sd ? (1 + d * sd)/2 : center_distance;
199 } while (flip (&d) != DOWN);
201 SCM func = me->get_grob_property ("dir-function");
202 SCM s = gh_call2 (func,
203 gh_cons (gh_int2scm (count[UP]),
204 gh_int2scm (count[DOWN])),
205 gh_cons (gh_int2scm (total[UP]),
206 gh_int2scm (total[DOWN])));
208 if (gh_number_p (s) && gh_scm2int (s))
211 /* If dir is not determined: get default */
212 return to_dir (me->get_grob_property ("neutral-direction"));
216 /* Set all stems with non-forced direction to beam direction.
217 Urg: non-forced should become `without/with unforced' direction,
218 once stem gets cleaned-up. */
220 Beam::set_stem_directions (Grob *me)
222 Link_array<Item> stems
223 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
224 Direction d = Directional_element_interface::get (me);
226 for (int i=0; i <stems.size (); i++)
229 SCM force = s->remove_grob_property ("dir-forced");
230 if (!gh_boolean_p (force) || !gh_scm2bool (force))
231 Directional_element_interface::set (s, d);
235 /* Simplistic auto-knees; only consider vertical gap between two
238 `Forced' stem directions are ignored. If you don't want auto-knees,
239 don't set, or unset auto-knee-gap. */
241 Beam::consider_auto_knees (Grob *me)
243 SCM scm = me->get_grob_property ("auto-knee-gap");
245 if (gh_number_p (scm))
249 Real staff_space = Staff_symbol_referencer::staff_space (me);
250 Real gap = gh_scm2double (scm) / staff_space;
252 Direction d = Directional_element_interface::get (me);
253 Link_array<Item> stems=
254 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
256 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
257 for (int i=1; i < stems.size (); i++)
258 if (!Stem::invisible_b (stems[i]))
259 common = common->common_refpoint (stems[i], Y_AXIS);
262 for (int i=1; i < stems.size (); i++)
264 if (!Stem::invisible_b (stems[i-1]))
266 if (Stem::invisible_b (stems[l]))
268 if (Stem::invisible_b (stems[i]))
271 Real left = Stem::extremal_heads (stems[l])[d]
272 ->relative_coordinate (common, Y_AXIS);
273 Real right = Stem::extremal_heads (stems[i])[-d]
274 ->relative_coordinate (common, Y_AXIS);
276 Real dy = right - left;
280 knee_y = (right + left) / 2;
288 for (int i=0; i < stems.size (); i++)
290 if (Stem::invisible_b (stems[i]))
293 Real y = Stem::extremal_heads (stems[i])[d]
294 ->relative_coordinate (common, Y_AXIS);
296 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
297 s->set_grob_property ("dir-forced", SCM_BOOL_T);
303 /* Set stem's shorten property if unset.
306 take some y-position (chord/beam/nearest?) into account
307 scmify forced-fraction
311 why is shorten stored in beam, and not directly in stem?
315 Beam::set_stem_shorten (Grob *m)
317 Spanner*me = dynamic_cast<Spanner*> (m);
319 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
321 int multiplicity = get_multiplicity (me);
323 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
324 if (shorten == SCM_EOL)
327 int sz = scm_ilength (shorten);
329 Real staff_space = Staff_symbol_referencer::staff_space (me);
330 SCM shorten_elt = scm_list_ref (shorten,
331 gh_int2scm (multiplicity <? (sz - 1)));
332 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
334 /* your similar cute comment here */
335 shorten_f *= forced_fraction;
337 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
340 /* Call list of y-dy-callbacks, that handle setting of
341 grob-properties y, dy.
343 User may set grob-properties: y-position-hs and height-hs
344 (to be fixed) that override the calculated y and dy.
346 Because y and dy cannot be calculated and quanted separately, we
347 always calculate both, then check for user override. */
348 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
350 Beam::after_line_breaking (SCM smob)
352 Grob *me = unsmob_grob (smob);
354 /* Copy to mutable list. */
355 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
356 me->set_grob_property ("positions", s);
358 if (ly_car (s) != SCM_BOOL_F)
359 return SCM_UNSPECIFIED;
361 // one wonders if such genericity is necessary --hwn.
362 SCM callbacks = me->get_grob_property ("position-callbacks");
363 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
364 gh_call1 (ly_car (i), smob);
366 set_stem_lengths (me);
367 return SCM_UNSPECIFIED;
381 - Make all demerits customisable
383 - One sensible check per demerit (what's this --hwn)
385 - Add demerits for quants per se, as to forbid a specific quant
389 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
391 Beam::quanting (SCM smob)
393 Grob *me = unsmob_grob (smob);
395 SCM s = me->get_grob_property ("positions");
396 Real yl = gh_scm2double (gh_car (s));
397 Real yr = gh_scm2double (gh_cdr (s));
399 Real ss = Staff_symbol_referencer::staff_space (me);
400 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
401 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
404 SCM sdy = me->get_grob_property ("least-squares-dy");
405 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
408 Real sit = (thickness - slt) / 2;
410 Real hang = 1.0 - (thickness - slt) / 2;
411 Real quants [] = {straddle, sit, inter, hang };
413 int num_quants = int (sizeof (quants)/sizeof (Real));
418 going to REGION_SIZE == 2, yields another 0.6 second with
422 (result indexes between 70 and 575) ? --hwn.
426 const int REGION_SIZE = 3;
427 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
428 for (int j = 0; j < num_quants; j++)
430 quantsl.push (i + quants[j] + int (yl));
431 quantsr.push (i + quants[j] + int (yr));
434 Array<Quant_score> qscores;
436 for (int l =0; l < quantsl.size (); l++)
437 for (int r =0; r < quantsr.size (); r++)
449 This is a longish function, but we don't separate this out into
450 neat modular separate subfunctions, as the subfunctions would be
451 called for many values of YL, YR. By precomputing various
452 parameters outside of the loop, we can save a lot of time.
455 for (int i = qscores.size (); i--;)
456 if (qscores[i].demerits < 100)
459 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
463 Real rad = Staff_symbol_referencer::staff_radius (me);
464 int multiplicity = get_multiplicity (me);
465 Real interbeam = multiplicity < 4
466 ? (2*ss + slt - thickness) / 2.0
467 : (3*ss + slt - thickness) / 3.0;
469 for (int i = qscores.size (); i--;)
470 if (qscores[i].demerits < 100)
473 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
474 rad, slt, thickness, interbeam,
480 Do stem lengths. These depend on YL and YR linearly, so we can
481 precompute for every stem 2 factors.
483 Link_array<Grob> stems=
484 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
485 Array<Stem_info> stem_infos;
486 Array<Real> lbase_lengths;
487 Array<Real> rbase_lengths;
489 Array<int> directions;
490 for (int i= 0; i < stems.size(); i++)
493 stem_infos.push( Stem::calc_stem_info (s));
495 Real b = calc_stem_y (me, s, Interval (1,0));
496 lbase_lengths.push (b);
498 b = calc_stem_y (me, s, Interval (0,1));
499 rbase_lengths.push (b);
500 directions.push( Directional_element_interface::get( s));
503 for (int i = qscores.size (); i--;)
504 if (qscores[i].demerits < 100)
507 += score_stem_lengths (stems, stem_infos,
508 lbase_lengths, rbase_lengths,
510 me, qscores[i].yl, qscores[i].yr);
516 for (int i = qscores.size (); i--;)
518 if (qscores[i].demerits < best)
520 best = qscores [i].demerits ;
526 me->set_grob_property ("positions",
527 gh_cons (gh_double2scm (qscores[best_idx].yl),
528 gh_double2scm (qscores[best_idx].yr))
534 me->set_grob_property ("quant-score",
535 gh_double2scm (qscores[best_idx].demerits));
536 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
539 return SCM_UNSPECIFIED;
543 Beam::score_stem_lengths (Link_array<Grob>stems,
544 Array<Stem_info> stem_infos,
545 Array<Real> left_factor,
546 Array<Real> right_factor,
547 Array<int> directions,
548 Grob*me, Real yl, Real yr)
550 Real demerit_score = 0.0 ;
552 for (int i=0; i < stems.size (); i++)
555 if (Stem::invisible_b (s))
559 yl * left_factor[i] + right_factor[i]* yr;
561 Stem_info info = stem_infos[i];
562 Direction d = Direction (directions[i]);
564 demerit_score += STEM_LENGTH_LIMIT_PENALTY * ( 0 >? (info.min_y - d * current_y));
565 demerit_score += STEM_LENGTH_LIMIT_PENALTY * ( 0 >? (d * current_y - info.max_y));
567 demerit_score += STEM_LENGTH_DEMERIT_FACTOR * shrink_extra_weight (d * current_y - info.ideal_y);
570 demerit_score *= 2.0 / stems.size ();
572 return demerit_score;
576 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
577 Real dy_mus, Real dy_damp)
582 if (sign (dy_damp) != sign (dy))
584 dem += DAMPING_DIRECTIION_PENALTY;
587 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
590 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
597 return x - floor (x);
601 Beam::score_forbidden_quants (Grob*me,
605 Real thickness, Real interbeam,
611 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
612 dem += INTER_QUANT_PENALTY;
613 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
614 dem += INTER_QUANT_PENALTY;
616 // todo: use multiplicity of outer stems.
617 if (multiplicity >= 2)
621 Real sit = (thickness - slt) / 2;
623 Real hang = 1.0 - (thickness - slt) / 2;
625 Direction dir = Directional_element_interface::get (me);
626 if (fabs (yl - dir * interbeam) < rad
627 && fabs (my_modf (yl) - inter) < 1e-3)
628 dem += SECONDARY_BEAM_DEMERIT;
629 if (fabs (yr - dir * interbeam) < rad
630 && fabs (my_modf (yr) - inter) < 1e-3)
631 dem += SECONDARY_BEAM_DEMERIT;
636 Can't we simply compute the distance between the nearest
637 staffline and the secondary beam? That would get rid of the
638 silly case analysis here (which is probably not when we have
639 different beam-thicknesses.)
645 // hmm, without Interval/Drul_array, you get ~ 4x same code...
646 if (fabs (yl - dir * interbeam) < rad + inter)
648 if (dir == UP && dy <= eps
649 && fabs (my_modf (yl) - sit) < eps)
650 dem += SECONDARY_BEAM_DEMERIT;
652 if (dir == DOWN && dy >= eps
653 && fabs (my_modf (yl) - hang) < eps)
654 dem += SECONDARY_BEAM_DEMERIT;
657 if (fabs (yr - dir * interbeam) < rad + inter)
659 if (dir == UP && dy >= eps
660 && fabs (my_modf (yr) - sit) < eps)
661 dem += SECONDARY_BEAM_DEMERIT;
663 if (dir == DOWN && dy <= eps
664 && fabs (my_modf (yr) - hang) < eps)
665 dem += SECONDARY_BEAM_DEMERIT;
668 if (multiplicity >= 3)
670 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
672 if (dir == UP && dy <= eps
673 && fabs (my_modf (yl) - straddle) < eps)
674 dem += SECONDARY_BEAM_DEMERIT;
676 if (dir == DOWN && dy >= eps
677 && fabs (my_modf (yl) - straddle) < eps)
678 dem += SECONDARY_BEAM_DEMERIT;
681 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
683 if (dir == UP && dy >= eps
684 && fabs (my_modf (yr) - straddle) < eps)
685 dem += SECONDARY_BEAM_DEMERIT;
687 if (dir == DOWN && dy <= eps
688 && fabs (my_modf (yr) - straddle) < eps)
689 dem += SECONDARY_BEAM_DEMERIT;
699 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
701 Beam::least_squares (SCM smob)
703 Grob *me = unsmob_grob (smob);
705 int count = visible_stem_count (me);
710 me->set_grob_property ("positions", ly_interval2scm (pos));
711 return SCM_UNSPECIFIED;
714 Direction dir = Directional_element_interface::get (me);
716 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
717 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
721 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
722 Stem::chord_start_f (last_visible_stem (me)));
726 TODO : use scoring for this.
728 complicated, because we take stem-info.ideal for determining
732 /* Make simple beam on middle line have small tilt */
733 if (!ideal[LEFT] && chord.delta () && count == 2)
735 Direction d = (Direction) (sign (chord.delta ()) * dir);
736 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
749 Array<Offset> ideals;
751 // ugh -> use commonx
752 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
753 Link_array<Item> stems=
754 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
756 for (int i=0; i < stems.size (); i++)
759 if (Stem::invisible_b (s))
761 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
762 Stem::calc_stem_info (s).ideal_y));
766 minimise_least_squares (&dydx, &y, ideals);
768 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
770 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
772 pos = Interval (y*dir, (y+dy) * dir);
775 me->set_grob_property ("positions", ly_interval2scm (pos));
776 return SCM_UNSPECIFIED;
779 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
781 Beam::check_concave (SCM smob)
783 Grob *me = unsmob_grob (smob);
785 Link_array<Item> stems =
786 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
788 for (int i = 0; i < stems.size ();)
790 if (Stem::invisible_b (stems[i]))
796 if (stems.size () < 3)
797 return SCM_UNSPECIFIED;
800 /* Concaveness #1: If distance of an inner notehead to line between
801 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
802 beam is concave (Heinz Stolba).
804 In the case of knees, the line connecting outer heads is often
805 not related to the beam slope (it may even go in the other
806 direction). Skip the check when the outer stems point in
807 different directions. --hwn
810 bool concaveness1 = false;
811 SCM gap = me->get_grob_property ("concaveness-gap");
812 if (gh_number_p (gap)
813 && Stem::get_direction(stems.top ())
814 == Stem::get_direction(stems[0]))
816 Real r1 = gh_scm2double (gap);
817 Real dy = Stem::chord_start_f (stems.top ())
818 - Stem::chord_start_f (stems[0]);
821 Real slope = dy / (stems.size () - 1);
823 Real y0 = Stem::chord_start_f (stems[0]);
824 for (int i = 1; i < stems.size () - 1; i++)
826 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
836 /* Concaveness #2: Sum distances of inner noteheads that fall
837 outside the interval of the two outer noteheads */
838 Real concaveness2 = 0;
839 SCM thresh = me->get_grob_property ("concaveness-threshold");
840 Real r2 = infinity_f;
841 if (!concaveness1 && gh_number_p (thresh))
843 r2 = gh_scm2double (thresh);
845 Direction dir = Directional_element_interface::get (me);
848 Interval iv (Stem::chord_start_f (stems[0]),
849 Stem::chord_start_f (stems.top ()));
851 if (iv[MAX] < iv[MIN])
854 for (int i = 1; i < stems.size () - 1; i++)
857 Real f = Stem::chord_start_f (stems[i]);
858 if ((c = f - iv[MAX]) > 0)
860 else if ((c = f - iv[MIN]) < 0)
864 Ugh. This will mess up with knees. Direction should be
869 concaveness2 = concave / (stems.size () - 2);
870 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
871 to behave as baerenreiter. */
872 concaveness2 /= (stems.size () - 2);
875 /* TODO: some sort of damping iso -> plain horizontal */
876 if (concaveness1 || concaveness2 > r2)
878 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
879 Real r = pos.linear_combination (0);
880 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
881 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
884 return SCM_UNSPECIFIED;
887 /* This neat trick is by Werner Lemberg,
888 damped = tanh (slope)
889 corresponds with some tables in [Wanske] CHECKME */
890 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
892 Beam::slope_damping (SCM smob)
894 Grob *me = unsmob_grob (smob);
896 if (visible_stem_count (me) <= 1)
897 return SCM_UNSPECIFIED;
899 SCM s = me->get_grob_property ("damping");
900 int damping = gh_scm2int (s);
904 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
905 Real dy = pos.delta ();
907 // ugh -> use commonx
908 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
909 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
910 Real dydx = dy && dx ? dy/dx : 0;
911 dydx = 0.6 * tanh (dydx) / damping;
913 Real damped_dy = dydx * dx;
914 pos[LEFT] += (dy - damped_dy) / 2;
915 pos[RIGHT] -= (dy - damped_dy) / 2;
917 me->set_grob_property ("positions", ly_interval2scm (pos));
919 return SCM_UNSPECIFIED;
923 Calculate the Y position of the stem-end, given the Y-left, Y-right
924 in POS, and for stem S.
927 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
929 int beam_multiplicity = get_multiplicity (me);
930 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
932 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
933 Real interbeam = get_interbeam (me);
935 // ugh -> use commonx
936 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
937 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
938 Real dy = pos.delta ();
939 Real stem_y = (dy && dx
940 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
945 Direction dir = Directional_element_interface::get (me);
946 Direction sdir = Directional_element_interface::get (s);
951 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
953 // huh, why not for first visible?
955 Grob *last_visible = last_visible_stem (me);
958 if ( Staff_symbol_referencer::staff_symbol_l (s)
959 != Staff_symbol_referencer::staff_symbol_l (last_visible))
960 stem_y += Directional_element_interface::get (me)
961 * (beam_multiplicity - stem_multiplicity) * interbeam;
964 programming_error ("No last visible stem");
971 Hmm. At this time, beam position and slope are determined. Maybe,
972 stem directions and length should set to relative to the chord's
973 position of the beam. */
975 Beam::set_stem_lengths (Grob *me)
977 Link_array<Item> stems=
978 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
980 if (stems.size () <= 1)
983 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
984 for (int i=1; i < stems.size (); i++)
985 if (!Stem::invisible_b (stems[i]))
986 common = common->common_refpoint (stems[i], Y_AXIS);
988 Direction dir = Directional_element_interface::get (me);
989 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
990 Real staff_space = Staff_symbol_referencer::staff_space (me);
991 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
992 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
993 for (int i=0; i < stems.size (); i++)
996 if (Stem::invisible_b (s))
999 Real stem_y = calc_stem_y (me, s, pos);
1001 // doesn't play well with dvips
1003 if (Stem::get_direction (s) == dir)
1004 stem_y += Stem::get_direction (s) * thick / 2;
1006 /* caution: stem measures in staff-positions */
1007 Real id = me->relative_coordinate (common, Y_AXIS)
1008 - stems[i]->relative_coordinate (common, Y_AXIS);
1009 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1014 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1016 Link_array<Grob> stems=
1017 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1020 for (int i=0; i < stems.size (); i++)
1024 /* Don't overwrite user override (?) */
1025 if (Stem::beam_count (stems[i], d) == -1
1026 /* Don't set beaming for outside of outer stems */
1027 && ! (d == LEFT && i == 0)
1028 && ! (d == RIGHT && i == stems.size () -1))
1030 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1031 Stem::set_beaming (stems[i], b, d);
1034 while (flip (&d) != LEFT);
1041 beams to go with one stem.
1046 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1048 // ugh -> use commonx
1050 && ! (next->relative_coordinate (0, X_AXIS)
1051 > here->relative_coordinate (0, X_AXIS)))
1053 && ! (prev->relative_coordinate (0, X_AXIS)
1054 < here->relative_coordinate (0, X_AXIS))))
1055 programming_error ("Beams are not left-to-right");
1057 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1058 Real bdy = get_interbeam (me);
1061 Molecule rightbeams;
1064 if (!Stem::first_head (here))
1067 int t = Stem::type_i (here);
1069 SCM proc = me->get_grob_property ("flag-width-function");
1070 SCM result = gh_call1 (proc, gh_int2scm (t));
1071 nw_f = gh_scm2double (result);
1075 Direction dir = Directional_element_interface::get (me);
1077 /* [Tremolo] beams on whole notes may not have direction set? */
1079 dir = Directional_element_interface::get (here);
1082 /* half beams extending to the left. */
1085 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1086 - Stem::beam_count (prev, RIGHT);
1087 int lwholebeams= Stem::beam_count (here, LEFT)
1088 <? Stem::beam_count (prev, RIGHT);
1090 /* Half beam should be one note-width,
1091 but let's make sure two half-beams never touch */
1093 // FIXME: TODO (check) stem width / sloped beams
1094 Real w = here->relative_coordinate (0, X_AXIS)
1095 - prev->relative_coordinate (0, X_AXIS);
1096 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1098 * me->paper_l ()->get_var ("linethickness");
1102 if (lhalfs) // generates warnings if not
1103 a = Lookup::beam (dydx, w + stem_w, thick);
1104 a.translate (Offset (-w, -w * dydx));
1105 a.translate_axis (-stem_w/2, X_AXIS);
1106 for (int j = 0; j < lhalfs; j++)
1109 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1110 leftbeams.add_molecule (b);
1116 int rhalfs = Stem::beam_count (here, RIGHT)
1117 - Stem::beam_count (next, LEFT);
1118 int rwholebeams= Stem::beam_count (here, RIGHT)
1119 <? Stem::beam_count (next, LEFT);
1121 Real w = next->relative_coordinate (0, X_AXIS)
1122 - here->relative_coordinate (0, X_AXIS);
1124 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1126 * me->paper_l ()->get_var ("linethickness");
1128 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1129 a.translate_axis (- stem_w/2, X_AXIS);
1133 SCM gap = me->get_grob_property ("gap");
1134 if (gh_number_p (gap))
1136 int gap_i = gh_scm2int ((gap));
1137 int nogap = rwholebeams - gap_i;
1139 for (; j < nogap; j++)
1142 b.translate_axis (-dir * bdy * j, Y_AXIS);
1143 rightbeams.add_molecule (b);
1145 if (Stem::invisible_b (here))
1150 a = Lookup::beam (dydx, w + stem_w, thick);
1153 for (; j < rwholebeams; j++)
1157 if (Stem::invisible_b (here))
1158 // ugh, see chord-tremolo.ly
1159 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1162 b.translate (Offset (tx, -dir * bdy * j));
1163 rightbeams.add_molecule (b);
1168 a = Lookup::beam (dydx, w, thick);
1170 for (; j < rwholebeams + rhalfs; j++)
1173 b.translate_axis (- dir * bdy * j, Y_AXIS);
1174 rightbeams.add_molecule (b);
1178 leftbeams.add_molecule (rightbeams);
1180 /* Does beam quanting think of the asymetry of beams?
1181 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1186 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1188 Beam::brew_molecule (SCM smob)
1190 Grob *me =unsmob_grob (smob);
1193 if (!gh_pair_p (me->get_grob_property ("stems")))
1196 Link_array<Item>stems =
1197 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1198 if (visible_stem_count (me))
1200 // ugh -> use commonx
1201 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1202 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1206 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1207 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1210 SCM posns = me->get_grob_property ("positions");
1212 if (!ly_number_pair_p (posns))
1214 programming_error ("No beam posns");
1215 pos = Interval (0,0);
1218 pos= ly_scm2interval (posns);
1219 Real dy = pos.delta ();
1220 Real dydx = dy && dx ? dy/dx : 0;
1222 for (int i=0; i < stems.size (); i++)
1224 Item *item = stems[i];
1225 Item *prev = (i > 0)? stems[i-1] : 0;
1226 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1228 Molecule sb = stem_beams (me, item, next, prev, dydx);
1229 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1230 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1231 mol.add_molecule (sb);
1234 mol.translate_axis (x0
1235 - dynamic_cast<Spanner*> (me)
1236 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1242 This code prints the demerits for each beam. Perhaps this
1243 should be switchable for those who want to twiddle with the
1249 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1252 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1255 SCM properties = Font_interface::font_alist_chain (me);
1258 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1259 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1262 return mol.smobbed_copy ();
1266 Beam::forced_stem_count (Grob *me)
1268 Link_array<Item>stems =
1269 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1271 for (int i=0; i < stems.size (); i++)
1275 if (Stem::invisible_b (s))
1278 if (((int)Stem::chord_start_f (s))
1279 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1289 Beam::visible_stem_count (Grob *me)
1291 Link_array<Item>stems =
1292 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1294 for (int i = stems.size (); i--;)
1296 if (!Stem::invisible_b (stems[i]))
1303 Beam::first_visible_stem (Grob *me)
1305 Link_array<Item>stems =
1306 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1308 for (int i = 0; i < stems.size (); i++)
1310 if (!Stem::invisible_b (stems[i]))
1317 Beam::last_visible_stem (Grob *me)
1319 Link_array<Item>stems =
1320 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1321 for (int i = stems.size (); i--;)
1323 if (!Stem::invisible_b (stems[i]))
1333 handle rest under beam (do_post: beams are calculated now)
1334 what about combination of collisions and rest under beam.
1338 rest -> stem -> beam -> interpolate_y_position ()
1340 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1342 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1344 Grob *rest = unsmob_grob (element_smob);
1345 Axis a = (Axis) gh_scm2int (axis);
1347 assert (a == Y_AXIS);
1349 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1352 return gh_double2scm (0.0);
1353 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1355 || !Beam::has_interface (beam)
1356 || !Beam::visible_stem_count (beam))
1357 return gh_double2scm (0.0);
1359 // make callback for rest from this.
1360 // todo: make sure this calced already.
1362 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1363 Interval pos (0, 0);
1364 SCM s = beam->get_grob_property ("positions");
1365 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1366 pos = ly_scm2interval (s);
1368 Real dy = pos.delta ();
1369 // ugh -> use commonx
1370 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1371 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1372 Real dydx = dy && dx ? dy/dx : 0;
1374 Direction d = Stem::get_direction (stem);
1375 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1377 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1380 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1383 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1385 minimum_dist + -d * (beamy - rest_dim) >? 0;
1387 int stafflines = Staff_symbol_referencer::line_count (rest);
1389 // move discretely by half spaces.
1390 int discrete_dist = int (ceil (dist));
1392 // move by whole spaces inside the staff.
1393 if (discrete_dist < stafflines+1)
1394 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1396 return gh_double2scm (-d * discrete_dist);
1402 ADD_INTERFACE (Beam, "beam-interface",
1405 #'thickness= weight of beams, in staffspace
1408 We take the least squares line through the ideal-length stems, and
1409 then damp that using
1411 damped = tanh (slope)
1413 this gives an unquantized left and right position for the beam end.
1414 Then we take all combinations of quantings near these left and right
1415 positions, and give them a score (according to how close they are to
1416 the ideal slope, how close the result is to the ideal stems, etc.). We
1417 take the best scoring combination.
1420 "position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy direction damping flag-width-function neutral-direction positions space-function thickness");