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>
17 * Use Number_pair i.s.o Interval to represent (yl, yr).
19 - Determine auto knees based on positions if it's set by the user.
25 - Stems run to the Y-center of the beam.
27 - beam_translation is the offset between Y centers of the beam.
32 #include <math.h> // tanh.
34 #include "molecule.hh"
35 #include "directional-element-interface.hh"
39 #include "least-squares.hh"
41 #include "paper-def.hh"
43 #include "group-interface.hh"
44 #include "staff-symbol-referencer.hh"
50 #define DEBUG_QUANTING 0
54 #include "text-item.hh" // debug output.
55 #include "font-interface.hh" // debug output.
60 Beam::add_stem (Grob *me, Grob *s)
62 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
64 s->add_dependency (me);
66 assert (!Stem::beam_l (s));
67 s->set_grob_property ("beam", me->self_scm ());
69 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
74 this returns the translation between 2 adjoining beams.
77 Beam::get_beam_translation (Grob *me)
79 SCM func = me->get_grob_property ("space-function");
80 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
81 return gh_scm2double (s);
88 Beam::get_beam_count (Grob *me)
91 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
93 Grob *sc = unsmob_grob (ly_car (s));
95 m = m >? (Stem::beam_multiplicity (sc).length () + 1);
100 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
102 Beam::space_function (SCM smob, SCM beam_count)
104 Grob *me = unsmob_grob (smob);
106 Real staff_space = Staff_symbol_referencer::staff_space (me);
107 Real line = me->paper_l ()->get_var ("linethickness");
108 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
111 Real beam_translation = gh_scm2int (beam_count) < 4
112 ? (2*staff_space + line - thickness) / 2.0
113 : (3*staff_space + line - thickness) / 3.0;
115 return gh_double2scm (beam_translation);
119 /* After pre-processing all directions should be set.
120 Several post-processing routines (stem, slur, script) need stem/beam
122 Currenly, this means that beam has set all stem's directions.
123 [Alternatively, stems could set its own directions, according to
124 their beam, during 'final-pre-processing'.] */
125 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
127 Beam::before_line_breaking (SCM smob)
129 Grob *me = unsmob_grob (smob);
131 /* Beams with less than 2 two stems don't make much sense, but could happen
136 For a beam that only has one stem, we try to do some disappearance magic:
137 we revert the flag, and move on to The Eternal Engraving Fields. */
139 int count = visible_stem_count (me);
142 me->warning (_ ("beam has less than two visible stems"));
144 SCM stems = me->get_grob_property ("stems");
145 if (scm_ilength (stems) == 1)
147 me->warning (_ ("Beam has less than two stems. Removing beam."));
149 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
152 return SCM_UNSPECIFIED;
154 else if (scm_ilength (stems) == 0)
157 return SCM_UNSPECIFIED;
162 Direction d = get_default_dir (me);
164 consider_auto_knees (me, d);
165 set_stem_directions (me, d);
169 set_stem_shorten (me);
178 Beam::connect_beams (Grob *me)
180 Link_array<Grob> stems=
181 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
184 last_int.set_empty();
185 for (int i = 0; i< stems.size(); i++)
187 Grob *this_stem = stems[i];
188 SCM this_beaming = this_stem->get_grob_property ("beaming");
190 Direction this_dir = Directional_element_interface::get(this_stem);
193 int start_point = last_int [this_dir];
199 if (d == RIGHT && i == stems.size()-1)
202 new_slice.set_empty();
203 SCM s = index_get_cell (this_beaming, d);
204 for (; gh_pair_p (s); s = gh_cdr (s))
207 start_point - this_dir * gh_scm2int (gh_car (s));
209 new_slice.add_point (new_beam_pos);
210 gh_set_car_x (s, gh_int2scm (new_beam_pos));
213 while (flip (&d) != LEFT);
215 if (!new_slice.empty_b())
216 last_int = new_slice;
220 gh_set_car_x ( this_beaming, SCM_EOL);
221 SCM s = gh_cdr (this_beaming);
222 for (; gh_pair_p (s); s = gh_cdr (s))
224 int np = - this_dir * gh_scm2int (gh_car(s));
225 gh_set_car_x (s, gh_int2scm (np));
226 last_int.add_point (np);
230 if (i == stems.size () -1)
232 gh_set_cdr_x ( this_beaming, SCM_EOL);
237 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
239 Beam::brew_molecule (SCM grob)
241 Grob *me = unsmob_grob (grob);
242 Link_array<Grob> stems=
243 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
244 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
247 if (visible_stem_count (me))
249 // ugh -> use commonx
250 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
251 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
255 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
256 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
259 SCM posns = me->get_grob_property ("positions");
261 if (!ly_number_pair_p (posns))
263 programming_error ("No beam posns");
264 pos = Interval (0,0);
267 pos= ly_scm2interval (posns);
269 Real dy = pos.delta ();
270 Real dydx = dy && dx ? dy/dx : 0;
272 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
273 Real bdy = get_beam_translation (me);
275 SCM last_beaming = SCM_EOL;;
276 Real last_xposn = -1;
277 Real last_width = -1 ;
280 SCM gap = me->get_grob_property ("gap");
282 Real lt = me->paper_l ()->get_var ("linethickness");
283 for (int i = 0; i< stems.size(); i++)
287 SCM this_beaming = st->get_grob_property ("beaming");
288 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
289 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
293 SCM left = gh_cdr (last_beaming);
294 SCM right = gh_car (this_beaming);
296 Array<int> fullbeams;
297 Array<int> lfliebertjes;
298 Array<int> rfliebertjes;
301 gh_pair_p (s); s =gh_cdr (s))
303 int b = gh_scm2int (gh_car (s));
304 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
310 lfliebertjes.push (b);
314 gh_pair_p (s); s =gh_cdr (s))
316 int b = gh_scm2int (gh_car (s));
317 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
319 rfliebertjes.push (b);
324 Real w = xposn - last_xposn;
325 Real stem_offset = 0.0;
326 Real width_corr = 0.0;
329 stem_offset -= last_width/2;
330 width_corr += last_width/2;
333 if (i == stems.size() -1)
335 width_corr += stem_width/2;
338 if (gh_number_p (gap))
340 Real g = gh_scm2double (gap);
345 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
346 for (int j = fullbeams.size(); j--;)
349 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
350 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
351 the_beam.add_molecule (b);
354 if (lfliebertjes.size() || rfliebertjes.size())
358 if (!Stem::first_head (st))
362 int t = Stem::duration_log (st);
364 SCM proc = me->get_grob_property ("flag-width-function");
365 SCM result = gh_call1 (proc, gh_int2scm (t));
366 nw_f = gh_scm2double (result);
369 /* Half beam should be one note-width,
370 but let's make sure two half-beams never touch */
372 Real w = xposn - last_xposn;
375 Molecule half = Lookup::beam (dydx, w, thick);
376 for (int j = lfliebertjes.size(); j--;)
379 b.translate_axis (last_xposn - x0, X_AXIS);
380 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
381 the_beam.add_molecule (b);
383 for (int j = rfliebertjes.size(); j--;)
386 b.translate_axis (xposn - x0 - w , X_AXIS);
387 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
388 the_beam.add_molecule (b);
394 last_width = stem_width;
395 last_beaming = this_beaming;
398 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
399 the_beam.translate_axis (pos[LEFT], Y_AXIS);
404 This code prints the demerits for each beam. Perhaps this
405 should be switchable for those who want to twiddle with the
411 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
414 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
417 SCM properties = Font_interface::font_alist_chain (me);
420 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
421 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
427 return the_beam.smobbed_copy();
434 Beam::get_default_dir (Grob *me)
436 Drul_array<int> total;
437 total[UP] = total[DOWN] = 0;
438 Drul_array<int> count;
439 count[UP] = count[DOWN] = 0;
442 Link_array<Item> stems=
443 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
445 for (int i=0; i <stems.size (); i++)
448 Direction sd = Directional_element_interface::get (s);
450 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
451 int current = sd ? (1 + d * sd)/2 : center_distance;
458 } while (flip (&d) != DOWN);
460 SCM func = me->get_grob_property ("dir-function");
461 SCM s = gh_call2 (func,
462 gh_cons (gh_int2scm (count[UP]),
463 gh_int2scm (count[DOWN])),
464 gh_cons (gh_int2scm (total[UP]),
465 gh_int2scm (total[DOWN])));
467 if (gh_number_p (s) && gh_scm2int (s))
470 /* If dir is not determined: get default */
471 return to_dir (me->get_grob_property ("neutral-direction"));
475 /* Set all stems with non-forced direction to beam direction.
476 Urg: non-forced should become `without/with unforced' direction,
477 once stem gets cleaned-up. */
479 Beam::set_stem_directions (Grob *me, Direction d)
481 Link_array<Item> stems
482 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
484 for (int i=0; i <stems.size (); i++)
487 /* For knees, non-forced stems should probably have their
488 natural direction. In any case, when knee, beam direction is
491 Stem::get_direction (s); // this actually sets it, if necessary
494 SCM force = s->remove_grob_property ("dir-forced");
495 if (!gh_boolean_p (force) || !gh_scm2bool (force))
496 Directional_element_interface::set (s, d);
501 /* Simplistic auto-knees; only consider vertical gap between two
504 This may decide for a knee that's impossible to fit sane scoring
505 criteria (eg, stem lengths). We may need something smarter. */
507 Beam::consider_auto_knees (Grob *me, Direction d)
509 SCM scm = me->get_grob_property ("auto-knee-gap");
511 if (!gh_number_p (scm))
516 Real staff_space = Staff_symbol_referencer::staff_space (me);
517 Real gap = gh_scm2double (scm) / staff_space;
519 Link_array<Grob> stems=
520 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
522 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
525 for (int r=1; r < stems.size (); r++)
527 if (!Stem::invisible_b (stems[r-1]))
529 Grob *right = stems[r];
530 Grob *left = stems[l];
531 if (Stem::invisible_b (left))
533 if (Stem::invisible_b (right))
536 Real left_y = Stem::extremal_heads (left)[d]
537 ->relative_coordinate (common, Y_AXIS);
538 Real right_y = Stem::extremal_heads (right)[-d]
539 ->relative_coordinate (common, Y_AXIS);
541 Real dy = right_y - left_y;
546 Direction knee_dir = (right_y > left_y ? UP : DOWN);
547 if (!Stem::invisible_b (left)
548 && left->get_grob_property ("dir-forced") != SCM_BOOL_T)
550 Directional_element_interface::set (left, knee_dir);
551 left->set_grob_property ("dir-forced", SCM_BOOL_T);
554 if (!Stem::invisible_b (right)
555 && stems[r]->get_grob_property ("dir-forced") != SCM_BOOL_T)
557 Directional_element_interface::set (right, -knee_dir);
558 right->set_grob_property ("dir-forced", SCM_BOOL_T);
565 me->set_grob_property ("knee", SCM_BOOL_T);
567 for (int i=0; i < stems.size (); i++)
568 stems[i]->set_grob_property ("stem-info", SCM_EOL);
572 /* Set stem's shorten property if unset.
575 take some y-position (chord/beam/nearest?) into account
576 scmify forced-fraction
578 This is done in beam because the shorten has to be uniform over the
583 Beam::set_stem_shorten (Grob *me)
586 shortening looks silly for x staff beams
591 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
593 int beam_count = get_beam_count (me);
595 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
596 if (shorten == SCM_EOL)
599 int sz = scm_ilength (shorten);
601 Real staff_space = Staff_symbol_referencer::staff_space (me);
602 SCM shorten_elt = scm_list_ref (shorten,
603 gh_int2scm (beam_count <? (sz - 1)));
604 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
606 /* your similar cute comment here */
607 shorten_f *= forced_fraction;
610 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
613 /* Call list of y-dy-callbacks, that handle setting of
617 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
619 Beam::after_line_breaking (SCM smob)
621 Grob *me = unsmob_grob (smob);
623 /* Copy to mutable list. */
624 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
625 me->set_grob_property ("positions", s);
627 if (ly_car (s) == SCM_BOOL_F)
630 // one wonders if such genericity is necessary --hwn.
631 SCM callbacks = me->get_grob_property ("position-callbacks");
632 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
633 gh_call1 (ly_car (i), smob);
636 set_stem_lengths (me);
637 return SCM_UNSPECIFIED;
640 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
642 Beam::least_squares (SCM smob)
644 Grob *me = unsmob_grob (smob);
646 int count = visible_stem_count (me);
651 me->set_grob_property ("positions", ly_interval2scm (pos));
652 return SCM_UNSPECIFIED;
656 Array<Real> x_posns ;
657 Link_array<Grob> stems=
658 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
659 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
660 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
662 Real my_y = me->relative_coordinate (commony, Y_AXIS);
664 Grob *fvs = first_visible_stem (me);
665 Grob *lvs = last_visible_stem (me);
667 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
668 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
669 Stem::calc_stem_info (lvs).ideal_y_
670 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
672 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
673 for (int i=0; i < stems.size (); i++)
677 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
680 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
688 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
689 Stem::chord_start_y (last_visible_stem (me)));
693 TODO -- use scoring for this.
695 complicated, because we take stem-info.ideal for determining
698 /* Make simple beam on middle line have small tilt */
699 if (!ideal[LEFT] && chord.delta () && count == 2)
705 Direction d = (Direction) (sign (chord.delta ()) * UP);
706 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
721 Array<Offset> ideals;
722 for (int i=0; i < stems.size (); i++)
725 if (Stem::invisible_b (s))
727 ideals.push (Offset (x_posns[i],
728 Stem::calc_stem_info (s).ideal_y_
729 + s->relative_coordinate (commony, Y_AXIS)
732 minimise_least_squares (&dydx, &y, ideals);
735 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
736 pos = Interval (y, (y+dy));
739 me->set_grob_property ("positions", ly_interval2scm (pos));
741 return SCM_UNSPECIFIED;
746 We can't combine with previous function, since check concave and
747 slope damping comes first.
749 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
751 Beam::shift_region_to_valid (SCM grob)
753 Grob *me = unsmob_grob (grob);
757 Array<Real> x_posns ;
758 Link_array<Grob> stems=
759 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
760 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
761 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
763 Grob *fvs = first_visible_stem (me);
766 return SCM_UNSPECIFIED;
768 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
769 for (int i=0; i < stems.size (); i++)
773 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
777 Grob *lvs = last_visible_stem (me);
779 return SCM_UNSPECIFIED;
781 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
783 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
784 Real dy = pos.delta();
790 Shift the positions so that we have a chance of finding good
791 quants (i.e. no short stem failures.)
793 Interval feasible_left_point;
794 feasible_left_point.set_full ();
795 for (int i=0; i < stems.size (); i++)
798 if (Stem::invisible_b (s))
801 Direction d = Stem::get_direction (s);
804 Stem::calc_stem_info (s).shortest_y_
805 - dydx * x_posns [i];
808 left_y is now relative to the stem S. We want relative to
809 ourselves, so translate:
812 + s->relative_coordinate (commony, Y_AXIS)
813 - me->relative_coordinate (commony, Y_AXIS);
819 feasible_left_point.intersect (flp);
822 if (feasible_left_point.empty_b())
824 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
826 else if (!feasible_left_point.elem_b(y))
828 if (isinf (feasible_left_point[DOWN]))
829 y = feasible_left_point[UP] - REGION_SIZE;
830 else if (isinf (feasible_left_point[UP]))
831 y = feasible_left_point[DOWN]+ REGION_SIZE;
833 y = feasible_left_point.center ();
835 pos = Interval (y, (y+dy));
836 me->set_grob_property ("positions", ly_interval2scm (pos));
837 return SCM_UNSPECIFIED;
841 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
843 Beam::check_concave (SCM smob)
845 Grob *me = unsmob_grob (smob);
847 Link_array<Grob> stems =
848 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
850 for (int i = 0; i < stems.size ();)
852 if (Stem::invisible_b (stems[i]))
858 if (stems.size () < 3)
859 return SCM_UNSPECIFIED;
862 /* Concaveness #1: If distance of an inner notehead to line between
863 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
864 beam is concave (Heinz Stolba).
866 In the case of knees, the line connecting outer heads is often
867 not related to the beam slope (it may even go in the other
868 direction). Skip the check when the outer stems point in
869 different directions. --hwn
872 bool concaveness1 = false;
873 SCM gap = me->get_grob_property ("concaveness-gap");
874 if (gh_number_p (gap)
875 && Stem::get_direction(stems.top ())
876 == Stem::get_direction(stems[0]))
878 Real r1 = gh_scm2double (gap);
879 Real dy = Stem::chord_start_y (stems.top ())
880 - Stem::chord_start_y (stems[0]);
883 Real slope = dy / (stems.size () - 1);
885 Real y0 = Stem::chord_start_y (stems[0]);
886 for (int i = 1; i < stems.size () - 1; i++)
888 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
898 /* Concaveness #2: Sum distances of inner noteheads that fall
899 outside the interval of the two outer noteheads.
901 We only do this for beams where first and last stem have the same
905 Note that "convex" stems compensate for "concave" stems.
906 (is that intentional?) --hwn.
909 Real concaveness2 = 0;
910 SCM thresh = me->get_grob_property ("concaveness-threshold");
911 Real r2 = infinity_f;
912 if (!concaveness1 && gh_number_p (thresh)
913 && Stem::get_direction(stems.top ())
914 == Stem::get_direction(stems[0]))
916 r2 = gh_scm2double (thresh);
918 Direction dir = Stem::get_direction(stems.top ());
920 Interval iv (Stem::chord_start_y (stems[0]),
921 Stem::chord_start_y (stems.top ()));
923 if (iv[MAX] < iv[MIN])
926 for (int i = 1; i < stems.size () - 1; i++)
928 Real f = Stem::chord_start_y (stems[i]);
929 concave += ((f - iv[MAX] ) >? 0) +
930 ((f - iv[MIN] ) <? 0);
933 concaveness2 = concave / (stems.size () - 2);
935 /* ugh: this is the a kludge to get
936 input/regression/beam-concave.ly to behave as
940 huh? we're dividing twice (which is not scalable) meaning that
941 the longer the beam, the more unlikely it will be
942 concave. Maybe you would even expect the other way around??
947 concaveness2 /= (stems.size () - 2);
950 /* TODO: some sort of damping iso -> plain horizontal */
951 if (concaveness1 || concaveness2 > r2)
953 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
954 Real r = pos.linear_combination (0);
955 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
956 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
959 return SCM_UNSPECIFIED;
962 /* This neat trick is by Werner Lemberg,
963 damped = tanh (slope)
964 corresponds with some tables in [Wanske] CHECKME */
965 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
967 Beam::slope_damping (SCM smob)
969 Grob *me = unsmob_grob (smob);
971 if (visible_stem_count (me) <= 1)
972 return SCM_UNSPECIFIED;
974 SCM s = me->get_grob_property ("damping");
975 int damping = gh_scm2int (s);
979 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
980 Real dy = pos.delta ();
982 Grob *fvs = first_visible_stem (me);
983 Grob *lvs = last_visible_stem (me);
985 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
988 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
989 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
990 Real dydx = dy && dx ? dy/dx : 0;
991 dydx = 0.6 * tanh (dydx) / damping;
993 Real damped_dy = dydx * dx;
994 pos[LEFT] += (dy - damped_dy) / 2;
995 pos[RIGHT] -= (dy - damped_dy) / 2;
997 me->set_grob_property ("positions", ly_interval2scm (pos));
999 return SCM_UNSPECIFIED;
1003 where_are_the_whole_beams(SCM beaming)
1007 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1009 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1011 l.add_point (gh_scm2int (gh_car (s)));
1018 Calculate the Y position of the stem-end, given the Y-left, Y-right
1019 in POS for stem S. This Y position is relative to S.
1022 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1024 Interval pos, bool french)
1026 Real beam_translation = get_beam_translation (me);
1029 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1030 Real dy = pos.delta ();
1032 Real stem_y_beam0 = (dy && dx
1037 Direction my_dir = Directional_element_interface::get (s);
1038 SCM beaming = s->get_grob_property ("beaming");
1040 Real stem_y = stem_y_beam0;
1043 Slice bm = where_are_the_whole_beams (beaming);
1045 stem_y += beam_translation * bm[-my_dir];
1049 Slice bm = Stem::beam_multiplicity(s);
1051 stem_y +=bm[my_dir] * beam_translation;
1054 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1055 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1061 Hmm. At this time, beam position and slope are determined. Maybe,
1062 stem directions and length should set to relative to the chord's
1063 position of the beam. */
1065 Beam::set_stem_lengths (Grob *me)
1067 Link_array<Grob> stems=
1068 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1070 if (stems.size () <= 1)
1074 for (int a = 2; a--;)
1075 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1077 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1078 Real staff_space = Staff_symbol_referencer::staff_space (me);
1080 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1085 if (gh_number_p (me->get_grob_property ("gap"))
1086 &&gh_scm2double (me->get_grob_property ("gap")))
1089 thick = gh_scm2double (me->get_grob_property ("thickness"))
1090 * Staff_symbol_referencer::staff_space(me);
1093 // ugh -> use commonx
1094 Grob * fvs = first_visible_stem (me);
1095 Grob *lvs = last_visible_stem (me);
1097 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1098 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1100 for (int i=0; i < stems.size (); i++)
1103 if (Stem::invisible_b (s))
1106 Real stem_y = calc_stem_y (me, s, common,
1108 pos, french && i > 0&& (i < stems.size () -1));
1111 Make the stems go up to the end of the beam. This doesn't matter
1112 for normal beams, but for tremolo beams it looks silly otherwise.
1115 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1117 Stem::set_stemend (s, 2* stem_y / staff_space);
1122 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1124 Link_array<Grob> stems=
1125 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1128 for (int i=0; i < stems.size (); i++)
1131 Don't overwrite user settings.
1136 /* Don't set beaming for outside of outer stems */
1137 if ((d == LEFT && i == 0)
1138 ||(d == RIGHT && i == stems.size () -1))
1142 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1143 if (beaming_prop == SCM_EOL ||
1144 index_get_cell (beaming_prop, d) == SCM_EOL)
1146 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1147 Stem::set_beaming (stems[i], b, d);
1150 while (flip (&d) != LEFT);
1155 Beam::forced_stem_count (Grob *me)
1157 Link_array<Grob>stems =
1158 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1160 for (int i=0; i < stems.size (); i++)
1164 if (Stem::invisible_b (s))
1167 if (((int)Stem::chord_start_y (s))
1168 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1178 Beam::visible_stem_count (Grob *me)
1180 Link_array<Grob>stems =
1181 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1183 for (int i = stems.size (); i--;)
1185 if (!Stem::invisible_b (stems[i]))
1192 Beam::first_visible_stem (Grob *me)
1194 Link_array<Grob>stems =
1195 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1197 for (int i = 0; i < stems.size (); i++)
1199 if (!Stem::invisible_b (stems[i]))
1206 Beam::last_visible_stem (Grob *me)
1208 Link_array<Grob>stems =
1209 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1210 for (int i = stems.size (); i--;)
1212 if (!Stem::invisible_b (stems[i]))
1222 handle rest under beam (do_post: beams are calculated now)
1223 what about combination of collisions and rest under beam.
1227 rest -> stem -> beam -> interpolate_y_position ()
1229 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1231 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1233 Grob *rest = unsmob_grob (element_smob);
1234 Axis a = (Axis) gh_scm2int (axis);
1236 assert (a == Y_AXIS);
1238 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1241 return gh_double2scm (0.0);
1242 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1244 || !Beam::has_interface (beam)
1245 || !Beam::visible_stem_count (beam))
1246 return gh_double2scm (0.0);
1248 // make callback for rest from this.
1249 // todo: make sure this calced already.
1251 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1252 Interval pos (0, 0);
1253 SCM s = beam->get_grob_property ("positions");
1254 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1255 pos = ly_scm2interval (s);
1257 Real dy = pos.delta ();
1258 // ugh -> use commonx
1259 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1260 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1261 Real dydx = dy && dx ? dy/dx : 0;
1263 Direction d = Stem::get_direction (stem);
1264 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1266 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1269 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1272 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1274 minimum_dist + -d * (beamy - rest_dim) >? 0;
1276 int stafflines = Staff_symbol_referencer::line_count (rest);
1278 // move discretely by half spaces.
1279 int discrete_dist = int (ceil (dist));
1281 // move by whole spaces inside the staff.
1282 if (discrete_dist < stafflines+1)
1283 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1285 return gh_double2scm (-d * discrete_dist);
1289 Beam::knee_b (Grob*me)
1291 SCM k = me->get_grob_property ("knee");
1292 if (gh_boolean_p (k))
1293 return gh_scm2bool (k);
1297 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1298 if (d != Directional_element_interface::get (unsmob_grob (ly_car (s))))
1304 me->set_grob_property ("knee", gh_bool2scm (knee));
1310 ADD_INTERFACE (Beam, "beam-interface",
1313 #'thickness= weight of beams, in staffspace
1316 We take the least squares line through the ideal-length stems, and
1317 then damp that using
1319 damped = tanh (slope)
1321 this gives an unquantized left and right position for the beam end.
1322 Then we take all combinations of quantings near these left and right
1323 positions, and give them a score (according to how close they are to
1324 the ideal slope, how close the result is to the ideal stems, etc.). We
1325 take the best scoring combination.
1328 "french-beaming 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");