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::get_beam (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->get_paper ()->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->get_paper ()->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_string (gh_scm2int (me->get_grob_property ("best-idx")));
414 str += to_string (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.to_str0 ()), 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 TODO: for x staff knees, set direction pointing to 'the
492 other' staff, rather than natural.
496 Stem::get_direction (s); // this actually sets it, if necessary
500 SCM force = s->remove_grob_property ("dir-forced");
501 if (!gh_boolean_p (force) || !gh_scm2bool (force))
502 Directional_element_interface::set (s, d);
507 /* Simplistic auto-knees; only consider vertical gap between two
510 This may decide for a knee that's impossible to fit sane scoring
511 criteria (eg, stem lengths). We may need something smarter. */
513 Beam::consider_auto_knees (Grob *me, Direction d)
515 SCM scm = me->get_grob_property ("auto-knee-gap");
517 if (!gh_number_p (scm))
522 Real staff_space = Staff_symbol_referencer::staff_space (me);
523 Real gap = gh_scm2double (scm) / staff_space;
525 Link_array<Grob> stems=
526 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
528 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
531 for (int r=1; r < stems.size (); r++)
533 if (!Stem::invisible_b (stems[r-1]))
535 Grob *right = stems[r];
536 Grob *left = stems[l];
537 if (Stem::invisible_b (left))
539 if (Stem::invisible_b (right))
542 Real left_y = Stem::extremal_heads (left)[d]
543 ->relative_coordinate (common, Y_AXIS);
544 Real right_y = Stem::extremal_heads (right)[-d]
545 ->relative_coordinate (common, Y_AXIS);
547 Real dy = right_y - left_y;
552 Direction knee_dir = (right_y > left_y ? UP : DOWN);
553 if (!Stem::invisible_b (left)
554 && left->get_grob_property ("dir-forced") != SCM_BOOL_T)
556 Directional_element_interface::set (left, knee_dir);
557 left->set_grob_property ("dir-forced", SCM_BOOL_T);
560 if (!Stem::invisible_b (right)
561 && stems[r]->get_grob_property ("dir-forced") != SCM_BOOL_T)
563 Directional_element_interface::set (right, -knee_dir);
564 right->set_grob_property ("dir-forced", SCM_BOOL_T);
571 me->set_grob_property ("knee", SCM_BOOL_T);
573 for (int i=0; i < stems.size (); i++)
574 stems[i]->set_grob_property ("stem-info", SCM_EOL);
578 /* Set stem's shorten property if unset.
581 take some y-position (chord/beam/nearest?) into account
582 scmify forced-fraction
584 This is done in beam because the shorten has to be uniform over the
589 Beam::set_stem_shorten (Grob *me)
592 shortening looks silly for x staff beams
597 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
599 int beam_count = get_beam_count (me);
601 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
602 if (shorten == SCM_EOL)
605 int sz = scm_ilength (shorten);
607 Real staff_space = Staff_symbol_referencer::staff_space (me);
608 SCM shorten_elt = scm_list_ref (shorten,
609 gh_int2scm (beam_count <? (sz - 1)));
610 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
612 /* your similar cute comment here */
613 shorten_f *= forced_fraction;
616 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
619 /* Call list of y-dy-callbacks, that handle setting of
623 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
625 Beam::after_line_breaking (SCM smob)
627 Grob *me = unsmob_grob (smob);
629 /* Copy to mutable list. */
630 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
631 me->set_grob_property ("positions", s);
633 if (ly_car (s) == SCM_BOOL_F)
636 // one wonders if such genericity is necessary --hwn.
637 SCM callbacks = me->get_grob_property ("position-callbacks");
638 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
639 gh_call1 (ly_car (i), smob);
642 set_stem_lengths (me);
643 return SCM_UNSPECIFIED;
646 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
648 Beam::least_squares (SCM smob)
650 Grob *me = unsmob_grob (smob);
652 int count = visible_stem_count (me);
657 me->set_grob_property ("positions", ly_interval2scm (pos));
658 return SCM_UNSPECIFIED;
662 Array<Real> x_posns ;
663 Link_array<Grob> stems=
664 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
665 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
666 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
668 Real my_y = me->relative_coordinate (commony, Y_AXIS);
670 Grob *fvs = first_visible_stem (me);
671 Grob *lvs = last_visible_stem (me);
673 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
674 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
675 Stem::calc_stem_info (lvs).ideal_y_
676 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
678 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
679 for (int i=0; i < stems.size (); i++)
683 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
686 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
694 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
695 Stem::chord_start_y (last_visible_stem (me)));
699 TODO -- use scoring for this.
701 complicated, because we take stem-info.ideal for determining
704 /* Make simple beam on middle line have small tilt */
705 if (!ideal[LEFT] && chord.delta () && count == 2)
711 Direction d = (Direction) (sign (chord.delta ()) * UP);
712 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
727 Array<Offset> ideals;
728 for (int i=0; i < stems.size (); i++)
731 if (Stem::invisible_b (s))
733 ideals.push (Offset (x_posns[i],
734 Stem::calc_stem_info (s).ideal_y_
735 + s->relative_coordinate (commony, Y_AXIS)
738 minimise_least_squares (&dydx, &y, ideals);
741 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
742 pos = Interval (y, (y+dy));
745 me->set_grob_property ("positions", ly_interval2scm (pos));
747 return SCM_UNSPECIFIED;
752 We can't combine with previous function, since check concave and
753 slope damping comes first.
755 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
757 Beam::shift_region_to_valid (SCM grob)
759 Grob *me = unsmob_grob (grob);
763 Array<Real> x_posns ;
764 Link_array<Grob> stems=
765 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
766 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
767 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
769 Grob *fvs = first_visible_stem (me);
772 return SCM_UNSPECIFIED;
774 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
775 for (int i=0; i < stems.size (); i++)
779 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
783 Grob *lvs = last_visible_stem (me);
785 return SCM_UNSPECIFIED;
787 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
789 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
790 Real dy = pos.delta();
796 Shift the positions so that we have a chance of finding good
797 quants (i.e. no short stem failures.)
799 Interval feasible_left_point;
800 feasible_left_point.set_full ();
801 for (int i=0; i < stems.size (); i++)
804 if (Stem::invisible_b (s))
807 Direction d = Stem::get_direction (s);
810 Stem::calc_stem_info (s).shortest_y_
811 - dydx * x_posns [i];
814 left_y is now relative to the stem S. We want relative to
815 ourselves, so translate:
818 + s->relative_coordinate (commony, Y_AXIS)
819 - me->relative_coordinate (commony, Y_AXIS);
825 feasible_left_point.intersect (flp);
828 if (feasible_left_point.empty_b())
830 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
832 else if (!feasible_left_point.elem_b(y))
834 if (isinf (feasible_left_point[DOWN]))
835 y = feasible_left_point[UP] - REGION_SIZE;
836 else if (isinf (feasible_left_point[UP]))
837 y = feasible_left_point[DOWN]+ REGION_SIZE;
839 y = feasible_left_point.center ();
841 pos = Interval (y, (y+dy));
842 me->set_grob_property ("positions", ly_interval2scm (pos));
843 return SCM_UNSPECIFIED;
847 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
849 Beam::check_concave (SCM smob)
851 Grob *me = unsmob_grob (smob);
853 Link_array<Grob> stems =
854 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
856 for (int i = 0; i < stems.size ();)
858 if (Stem::invisible_b (stems[i]))
864 if (stems.size () < 3)
865 return SCM_UNSPECIFIED;
868 /* Concaveness #1: If distance of an inner notehead to line between
869 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
870 beam is concave (Heinz Stolba).
872 In the case of knees, the line connecting outer heads is often
873 not related to the beam slope (it may even go in the other
874 direction). Skip the check when the outer stems point in
875 different directions. --hwn
878 bool concaveness1 = false;
879 SCM gap = me->get_grob_property ("concaveness-gap");
880 if (gh_number_p (gap)
881 && Stem::get_direction(stems.top ())
882 == Stem::get_direction(stems[0]))
884 Real r1 = gh_scm2double (gap);
885 Real dy = Stem::chord_start_y (stems.top ())
886 - Stem::chord_start_y (stems[0]);
889 Real slope = dy / (stems.size () - 1);
891 Real y0 = Stem::chord_start_y (stems[0]);
892 for (int i = 1; i < stems.size () - 1; i++)
894 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
904 /* Concaveness #2: Sum distances of inner noteheads that fall
905 outside the interval of the two outer noteheads.
907 We only do this for beams where first and last stem have the same
911 Note that "convex" stems compensate for "concave" stems.
912 (is that intentional?) --hwn.
915 Real concaveness2 = 0;
916 SCM thresh = me->get_grob_property ("concaveness-threshold");
917 Real r2 = infinity_f;
918 if (!concaveness1 && gh_number_p (thresh)
919 && Stem::get_direction(stems.top ())
920 == Stem::get_direction(stems[0]))
922 r2 = gh_scm2double (thresh);
924 Direction dir = Stem::get_direction(stems.top ());
926 Interval iv (Stem::chord_start_y (stems[0]),
927 Stem::chord_start_y (stems.top ()));
929 if (iv[MAX] < iv[MIN])
932 for (int i = 1; i < stems.size () - 1; i++)
934 Real f = Stem::chord_start_y (stems[i]);
935 concave += ((f - iv[MAX] ) >? 0) +
936 ((f - iv[MIN] ) <? 0);
939 concaveness2 = concave / (stems.size () - 2);
941 /* ugh: this is the a kludge to get
942 input/regression/beam-concave.ly to behave as
946 huh? we're dividing twice (which is not scalable) meaning that
947 the longer the beam, the more unlikely it will be
948 concave. Maybe you would even expect the other way around??
953 concaveness2 /= (stems.size () - 2);
956 /* TODO: some sort of damping iso -> plain horizontal */
957 if (concaveness1 || concaveness2 > r2)
959 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
960 Real r = pos.linear_combination (0);
961 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
962 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
965 return SCM_UNSPECIFIED;
968 /* This neat trick is by Werner Lemberg,
969 damped = tanh (slope)
970 corresponds with some tables in [Wanske] CHECKME */
971 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
973 Beam::slope_damping (SCM smob)
975 Grob *me = unsmob_grob (smob);
977 if (visible_stem_count (me) <= 1)
978 return SCM_UNSPECIFIED;
980 SCM s = me->get_grob_property ("damping");
981 int damping = gh_scm2int (s);
985 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
986 Real dy = pos.delta ();
988 Grob *fvs = first_visible_stem (me);
989 Grob *lvs = last_visible_stem (me);
991 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
994 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
995 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
996 Real dydx = dy && dx ? dy/dx : 0;
997 dydx = 0.6 * tanh (dydx) / damping;
999 Real damped_dy = dydx * dx;
1000 pos[LEFT] += (dy - damped_dy) / 2;
1001 pos[RIGHT] -= (dy - damped_dy) / 2;
1003 me->set_grob_property ("positions", ly_interval2scm (pos));
1005 return SCM_UNSPECIFIED;
1009 where_are_the_whole_beams(SCM beaming)
1013 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1015 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1017 l.add_point (gh_scm2int (gh_car (s)));
1024 Calculate the Y position of the stem-end, given the Y-left, Y-right
1025 in POS for stem S. This Y position is relative to S.
1028 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1030 Interval pos, bool french)
1032 Real beam_translation = get_beam_translation (me);
1035 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1036 Real dy = pos.delta ();
1038 Real stem_y_beam0 = (dy && dx
1043 Direction my_dir = Directional_element_interface::get (s);
1044 SCM beaming = s->get_grob_property ("beaming");
1046 Real stem_y = stem_y_beam0;
1049 Slice bm = where_are_the_whole_beams (beaming);
1051 stem_y += beam_translation * bm[-my_dir];
1055 Slice bm = Stem::beam_multiplicity(s);
1057 stem_y +=bm[my_dir] * beam_translation;
1060 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1061 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1067 Hmm. At this time, beam position and slope are determined. Maybe,
1068 stem directions and length should set to relative to the chord's
1069 position of the beam. */
1071 Beam::set_stem_lengths (Grob *me)
1073 Link_array<Grob> stems=
1074 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1076 if (stems.size () <= 1)
1080 for (int a = 2; a--;)
1081 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1083 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1084 Real staff_space = Staff_symbol_referencer::staff_space (me);
1086 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1091 if (gh_number_p (me->get_grob_property ("gap"))
1092 &&gh_scm2double (me->get_grob_property ("gap")))
1095 thick = gh_scm2double (me->get_grob_property ("thickness"))
1096 * Staff_symbol_referencer::staff_space(me);
1099 // ugh -> use commonx
1100 Grob * fvs = first_visible_stem (me);
1101 Grob *lvs = last_visible_stem (me);
1103 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1104 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1106 for (int i=0; i < stems.size (); i++)
1109 if (Stem::invisible_b (s))
1112 Real stem_y = calc_stem_y (me, s, common,
1114 pos, french && i > 0&& (i < stems.size () -1));
1117 Make the stems go up to the end of the beam. This doesn't matter
1118 for normal beams, but for tremolo beams it looks silly otherwise.
1121 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1123 Stem::set_stemend (s, 2* stem_y / staff_space);
1128 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1130 Link_array<Grob> stems=
1131 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1134 for (int i=0; i < stems.size (); i++)
1137 Don't overwrite user settings.
1142 /* Don't set beaming for outside of outer stems */
1143 if ((d == LEFT && i == 0)
1144 ||(d == RIGHT && i == stems.size () -1))
1148 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1149 if (beaming_prop == SCM_EOL ||
1150 index_get_cell (beaming_prop, d) == SCM_EOL)
1152 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1153 Stem::set_beaming (stems[i], b, d);
1156 while (flip (&d) != LEFT);
1161 Beam::forced_stem_count (Grob *me)
1163 Link_array<Grob>stems =
1164 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1166 for (int i=0; i < stems.size (); i++)
1170 if (Stem::invisible_b (s))
1173 if (((int)Stem::chord_start_y (s))
1174 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1184 Beam::visible_stem_count (Grob *me)
1186 Link_array<Grob>stems =
1187 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1189 for (int i = stems.size (); i--;)
1191 if (!Stem::invisible_b (stems[i]))
1198 Beam::first_visible_stem (Grob *me)
1200 Link_array<Grob>stems =
1201 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1203 for (int i = 0; i < stems.size (); i++)
1205 if (!Stem::invisible_b (stems[i]))
1212 Beam::last_visible_stem (Grob *me)
1214 Link_array<Grob>stems =
1215 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1216 for (int i = stems.size (); i--;)
1218 if (!Stem::invisible_b (stems[i]))
1228 handle rest under beam (do_post: beams are calculated now)
1229 what about combination of collisions and rest under beam.
1233 rest -> stem -> beam -> interpolate_y_position ()
1235 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1237 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1239 Grob *rest = unsmob_grob (element_smob);
1240 Axis a = (Axis) gh_scm2int (axis);
1242 assert (a == Y_AXIS);
1244 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1247 return gh_double2scm (0.0);
1248 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1250 || !Beam::has_interface (beam)
1251 || !Beam::visible_stem_count (beam))
1252 return gh_double2scm (0.0);
1254 // make callback for rest from this.
1255 // todo: make sure this calced already.
1257 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1258 Interval pos (0, 0);
1259 SCM s = beam->get_grob_property ("positions");
1260 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1261 pos = ly_scm2interval (s);
1263 Real dy = pos.delta ();
1264 // ugh -> use commonx
1265 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1266 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1267 Real dydx = dy && dx ? dy/dx : 0;
1269 Direction d = Stem::get_direction (stem);
1270 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1272 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1275 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1278 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1280 minimum_dist + -d * (beamy - rest_dim) >? 0;
1282 int stafflines = Staff_symbol_referencer::line_count (rest);
1284 // move discretely by half spaces.
1285 int discrete_dist = int (ceil (dist));
1287 // move by whole spaces inside the staff.
1288 if (discrete_dist < stafflines+1)
1289 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1291 return gh_double2scm (-d * discrete_dist);
1295 Beam::knee_b (Grob*me)
1297 SCM k = me->get_grob_property ("knee");
1298 if (gh_boolean_p (k))
1299 return gh_scm2bool (k);
1303 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1305 Direction dir = Directional_element_interface::get
1306 (unsmob_grob (ly_car (s)));
1315 me->set_grob_property ("knee", gh_bool2scm (knee));
1320 ADD_INTERFACE (Beam, "beam-interface",
1323 #'thickness= weight of beams, in staffspace
1326 We take the least squares line through the ideal-length stems, and
1327 then damp that using
1329 damped = tanh (slope)
1331 this gives an unquantized left and right position for the beam end.
1332 Then we take all combinations of quantings near these left and right
1333 positions, and give them a score (according to how close they are to
1334 the ideal slope, how close the result is to the ideal stems, etc.). We
1335 take the best scoring combination.
1338 "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");