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);
177 We want a maximal number of shared beams, but if there is choice, we
178 take the one that is closest to the end of the stem. This is for situations like
191 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
195 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
199 for (int i = lslice[-left_dir];
200 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
203 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
205 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
206 if (scm_memq (gh_int2scm (k), left_beaming) != SCM_BOOL_F)
210 if (count >= best_count)
221 Beam::connect_beams (Grob *me)
223 Link_array<Grob> stems=
224 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
227 last_int.set_empty();
228 SCM last_beaming = SCM_EOL;
229 Direction last_dir = CENTER;
230 for (int i = 0; i< stems.size(); i++)
232 Grob *this_stem = stems[i];
233 SCM this_beaming = this_stem->get_grob_property ("beaming");
235 Direction this_dir = Directional_element_interface::get(this_stem);
238 int start_point = position_with_maximal_common_beams
239 (last_beaming, this_beaming,
246 if (d == RIGHT && i == stems.size()-1)
249 new_slice.set_empty();
250 SCM s = index_get_cell (this_beaming, d);
251 for (; gh_pair_p (s); s = gh_cdr (s))
254 start_point - this_dir * gh_scm2int (gh_car (s));
256 new_slice.add_point (new_beam_pos);
257 gh_set_car_x (s, gh_int2scm (new_beam_pos));
262 while (flip (&d) != LEFT);
264 if (!new_slice.empty_b())
265 last_int = new_slice;
269 gh_set_car_x ( this_beaming, SCM_EOL);
270 SCM s = gh_cdr (this_beaming);
271 for (; gh_pair_p (s); s = gh_cdr (s))
273 int np = - this_dir * gh_scm2int (gh_car(s));
274 gh_set_car_x (s, gh_int2scm (np));
275 last_int.add_point (np);
279 if (i == stems.size () -1)
281 gh_set_cdr_x (this_beaming, SCM_EOL);
284 if (scm_ilength (gh_cdr (this_beaming)) > 0)
286 last_beaming = this_beaming;
292 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
294 Beam::brew_molecule (SCM grob)
296 Grob *me = unsmob_grob (grob);
297 Link_array<Grob> stems=
298 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
299 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
302 if (visible_stem_count (me))
304 // ugh -> use commonx
305 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
306 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
310 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
311 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
314 SCM posns = me->get_grob_property ("positions");
316 if (!ly_number_pair_p (posns))
318 programming_error ("No beam posns");
319 pos = Interval (0,0);
322 pos= ly_scm2interval (posns);
324 Real dy = pos.delta ();
325 Real dydx = dy && dx ? dy/dx : 0;
327 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
328 Real bdy = get_beam_translation (me);
330 SCM last_beaming = SCM_EOL;;
331 Real last_xposn = -1;
332 Real last_width = -1 ;
335 SCM gap = me->get_grob_property ("gap");
337 Real lt = me->get_paper ()->get_var ("linethickness");
338 for (int i = 0; i< stems.size(); i++)
342 SCM this_beaming = st->get_grob_property ("beaming");
343 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
344 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
348 SCM left = gh_cdr (last_beaming);
349 SCM right = gh_car (this_beaming);
351 Array<int> fullbeams;
352 Array<int> lfliebertjes;
353 Array<int> rfliebertjes;
356 gh_pair_p (s); s =gh_cdr (s))
358 int b = gh_scm2int (gh_car (s));
359 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
365 lfliebertjes.push (b);
369 gh_pair_p (s); s =gh_cdr (s))
371 int b = gh_scm2int (gh_car (s));
372 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
374 rfliebertjes.push (b);
379 Real w = xposn - last_xposn;
380 Real stem_offset = 0.0;
381 Real width_corr = 0.0;
384 stem_offset -= last_width/2;
385 width_corr += last_width/2;
388 if (i == stems.size() -1)
390 width_corr += stem_width/2;
393 if (gh_number_p (gap))
395 Real g = gh_scm2double (gap);
400 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
401 for (int j = fullbeams.size(); j--;)
404 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
405 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
406 the_beam.add_molecule (b);
409 if (lfliebertjes.size() || rfliebertjes.size())
413 if (!Stem::first_head (st))
417 int t = Stem::duration_log (st);
419 SCM proc = me->get_grob_property ("flag-width-function");
420 SCM result = gh_call1 (proc, gh_int2scm (t));
421 nw_f = gh_scm2double (result);
424 /* Half beam should be one note-width,
425 but let's make sure two half-beams never touch */
427 Real w = xposn - last_xposn;
430 Molecule half = Lookup::beam (dydx, w, thick);
431 for (int j = lfliebertjes.size(); j--;)
434 b.translate_axis (last_xposn - x0, X_AXIS);
435 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
436 the_beam.add_molecule (b);
438 for (int j = rfliebertjes.size(); j--;)
441 b.translate_axis (xposn - x0 - w , X_AXIS);
442 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
443 the_beam.add_molecule (b);
449 last_width = stem_width;
450 last_beaming = this_beaming;
453 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
454 the_beam.translate_axis (pos[LEFT], Y_AXIS);
459 This code prints the demerits for each beam. Perhaps this
460 should be switchable for those who want to twiddle with the
466 str += to_string (gh_scm2int (me->get_grob_property ("best-idx")));
469 str += to_string (gh_scm2double (me->get_grob_property ("quant-score")),
472 SCM properties = Font_interface::font_alist_chain (me);
475 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.to_str0 ()), properties);
476 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
482 return the_beam.smobbed_copy();
489 Beam::get_default_dir (Grob *me)
491 Drul_array<int> total;
492 total[UP] = total[DOWN] = 0;
493 Drul_array<int> count;
494 count[UP] = count[DOWN] = 0;
497 Link_array<Item> stems=
498 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
500 for (int i=0; i <stems.size (); i++)
503 Direction sd = Directional_element_interface::get (s);
505 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
506 int current = sd ? (1 + d * sd)/2 : center_distance;
513 } while (flip (&d) != DOWN);
515 SCM func = me->get_grob_property ("dir-function");
516 SCM s = gh_call2 (func,
517 gh_cons (gh_int2scm (count[UP]),
518 gh_int2scm (count[DOWN])),
519 gh_cons (gh_int2scm (total[UP]),
520 gh_int2scm (total[DOWN])));
522 if (gh_number_p (s) && gh_scm2int (s))
525 /* If dir is not determined: get default */
526 return to_dir (me->get_grob_property ("neutral-direction"));
530 /* Set all stems with non-forced direction to beam direction.
531 Urg: non-forced should become `without/with unforced' direction,
532 once stem gets cleaned-up. */
534 Beam::set_stem_directions (Grob *me, Direction d)
536 Link_array<Item> stems
537 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
539 for (int i=0; i <stems.size (); i++)
542 /* For knees, non-forced stems should probably have their
543 natural direction. In any case, when knee, beam direction is
546 TODO: for x staff knees, set direction pointing to 'the
547 other' staff, rather than natural.
551 Stem::get_direction (s); // this actually sets it, if necessary
555 SCM force = s->remove_grob_property ("dir-forced");
556 if (!gh_boolean_p (force) || !gh_scm2bool (force))
557 Directional_element_interface::set (s, d);
562 /* Simplistic auto-knees; only consider vertical gap between two
565 This may decide for a knee that's impossible to fit sane scoring
566 criteria (eg, stem lengths). We may need something smarter. */
568 Beam::consider_auto_knees (Grob *me, Direction d)
570 SCM scm = me->get_grob_property ("auto-knee-gap");
572 if (!gh_number_p (scm))
577 Real staff_space = Staff_symbol_referencer::staff_space (me);
578 Real gap = gh_scm2double (scm) / staff_space;
580 Link_array<Grob> stems=
581 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
583 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
586 for (int r=1; r < stems.size (); r++)
588 if (!Stem::invisible_b (stems[r-1]))
590 Grob *right = stems[r];
591 Grob *left = stems[l];
592 if (Stem::invisible_b (left))
594 if (Stem::invisible_b (right))
597 Real left_y = Stem::extremal_heads (left)[d]
598 ->relative_coordinate (common, Y_AXIS);
599 Real right_y = Stem::extremal_heads (right)[-d]
600 ->relative_coordinate (common, Y_AXIS);
602 Real dy = right_y - left_y;
607 Direction knee_dir = (right_y > left_y ? UP : DOWN);
608 if (!Stem::invisible_b (left)
609 && left->get_grob_property ("dir-forced") != SCM_BOOL_T)
611 Directional_element_interface::set (left, knee_dir);
612 left->set_grob_property ("dir-forced", SCM_BOOL_T);
615 if (!Stem::invisible_b (right)
616 && stems[r]->get_grob_property ("dir-forced") != SCM_BOOL_T)
618 Directional_element_interface::set (right, -knee_dir);
619 right->set_grob_property ("dir-forced", SCM_BOOL_T);
626 me->set_grob_property ("knee", SCM_BOOL_T);
628 for (int i=0; i < stems.size (); i++)
629 stems[i]->set_grob_property ("stem-info", SCM_EOL);
633 /* Set stem's shorten property if unset.
636 take some y-position (chord/beam/nearest?) into account
637 scmify forced-fraction
639 This is done in beam because the shorten has to be uniform over the
644 Beam::set_stem_shorten (Grob *me)
647 shortening looks silly for x staff beams
652 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
654 int beam_count = get_beam_count (me);
656 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
657 if (shorten == SCM_EOL)
660 int sz = scm_ilength (shorten);
662 Real staff_space = Staff_symbol_referencer::staff_space (me);
663 SCM shorten_elt = scm_list_ref (shorten,
664 gh_int2scm (beam_count <? (sz - 1)));
665 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
667 /* your similar cute comment here */
668 shorten_f *= forced_fraction;
671 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
674 /* Call list of y-dy-callbacks, that handle setting of
678 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
680 Beam::after_line_breaking (SCM smob)
682 Grob *me = unsmob_grob (smob);
684 /* Copy to mutable list. */
685 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
686 me->set_grob_property ("positions", s);
688 if (ly_car (s) == SCM_BOOL_F)
691 // one wonders if such genericity is necessary --hwn.
692 SCM callbacks = me->get_grob_property ("position-callbacks");
693 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
694 gh_call1 (ly_car (i), smob);
697 set_stem_lengths (me);
698 return SCM_UNSPECIFIED;
701 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
703 Beam::least_squares (SCM smob)
705 Grob *me = unsmob_grob (smob);
707 int count = visible_stem_count (me);
712 me->set_grob_property ("positions", ly_interval2scm (pos));
713 return SCM_UNSPECIFIED;
717 Array<Real> x_posns ;
718 Link_array<Grob> stems=
719 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
720 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
721 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
723 Real my_y = me->relative_coordinate (commony, Y_AXIS);
725 Grob *fvs = first_visible_stem (me);
726 Grob *lvs = last_visible_stem (me);
728 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
729 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
730 Stem::calc_stem_info (lvs).ideal_y_
731 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
733 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
734 for (int i=0; i < stems.size (); i++)
738 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
741 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
749 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
750 Stem::chord_start_y (last_visible_stem (me)));
754 TODO -- use scoring for this.
756 complicated, because we take stem-info.ideal for determining
759 /* Make simple beam on middle line have small tilt */
760 if (!ideal[LEFT] && chord.delta () && count == 2)
766 Direction d = (Direction) (sign (chord.delta ()) * UP);
767 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
782 Array<Offset> ideals;
783 for (int i=0; i < stems.size (); i++)
786 if (Stem::invisible_b (s))
788 ideals.push (Offset (x_posns[i],
789 Stem::calc_stem_info (s).ideal_y_
790 + s->relative_coordinate (commony, Y_AXIS)
793 minimise_least_squares (&dydx, &y, ideals);
796 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
797 pos = Interval (y, (y+dy));
800 me->set_grob_property ("positions", ly_interval2scm (pos));
802 return SCM_UNSPECIFIED;
807 We can't combine with previous function, since check concave and
808 slope damping comes first.
810 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
812 Beam::shift_region_to_valid (SCM grob)
814 Grob *me = unsmob_grob (grob);
818 Array<Real> x_posns ;
819 Link_array<Grob> stems=
820 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
821 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
822 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
824 Grob *fvs = first_visible_stem (me);
827 return SCM_UNSPECIFIED;
829 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
830 for (int i=0; i < stems.size (); i++)
834 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
838 Grob *lvs = last_visible_stem (me);
840 return SCM_UNSPECIFIED;
842 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
844 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
845 Real dy = pos.delta();
851 Shift the positions so that we have a chance of finding good
852 quants (i.e. no short stem failures.)
854 Interval feasible_left_point;
855 feasible_left_point.set_full ();
856 for (int i=0; i < stems.size (); i++)
859 if (Stem::invisible_b (s))
862 Direction d = Stem::get_direction (s);
865 Stem::calc_stem_info (s).shortest_y_
866 - dydx * x_posns [i];
869 left_y is now relative to the stem S. We want relative to
870 ourselves, so translate:
873 + s->relative_coordinate (commony, Y_AXIS)
874 - me->relative_coordinate (commony, Y_AXIS);
880 feasible_left_point.intersect (flp);
883 if (feasible_left_point.empty_b())
885 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
887 else if (!feasible_left_point.elem_b(y))
889 if (isinf (feasible_left_point[DOWN]))
890 y = feasible_left_point[UP] - REGION_SIZE;
891 else if (isinf (feasible_left_point[UP]))
892 y = feasible_left_point[DOWN]+ REGION_SIZE;
894 y = feasible_left_point.center ();
896 pos = Interval (y, (y+dy));
897 me->set_grob_property ("positions", ly_interval2scm (pos));
898 return SCM_UNSPECIFIED;
902 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
904 Beam::check_concave (SCM smob)
906 Grob *me = unsmob_grob (smob);
908 Link_array<Grob> stems =
909 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
911 for (int i = 0; i < stems.size ();)
913 if (Stem::invisible_b (stems[i]))
919 if (stems.size () < 3)
920 return SCM_UNSPECIFIED;
923 /* Concaveness #1: If distance of an inner notehead to line between
924 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
925 beam is concave (Heinz Stolba).
927 In the case of knees, the line connecting outer heads is often
928 not related to the beam slope (it may even go in the other
929 direction). Skip the check when the outer stems point in
930 different directions. --hwn
933 bool concaveness1 = false;
934 SCM gap = me->get_grob_property ("concaveness-gap");
935 if (gh_number_p (gap)
936 && Stem::get_direction(stems.top ())
937 == Stem::get_direction(stems[0]))
939 Real r1 = gh_scm2double (gap);
940 Real dy = Stem::chord_start_y (stems.top ())
941 - Stem::chord_start_y (stems[0]);
944 Real slope = dy / (stems.size () - 1);
946 Real y0 = Stem::chord_start_y (stems[0]);
947 for (int i = 1; i < stems.size () - 1; i++)
949 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
959 /* Concaveness #2: Sum distances of inner noteheads that fall
960 outside the interval of the two outer noteheads.
962 We only do this for beams where first and last stem have the same
966 Note that "convex" stems compensate for "concave" stems.
967 (is that intentional?) --hwn.
970 Real concaveness2 = 0;
971 SCM thresh = me->get_grob_property ("concaveness-threshold");
972 Real r2 = infinity_f;
973 if (!concaveness1 && gh_number_p (thresh)
974 && Stem::get_direction(stems.top ())
975 == Stem::get_direction(stems[0]))
977 r2 = gh_scm2double (thresh);
979 Direction dir = Stem::get_direction(stems.top ());
981 Interval iv (Stem::chord_start_y (stems[0]),
982 Stem::chord_start_y (stems.top ()));
984 if (iv[MAX] < iv[MIN])
987 for (int i = 1; i < stems.size () - 1; i++)
989 Real f = Stem::chord_start_y (stems[i]);
990 concave += ((f - iv[MAX] ) >? 0) +
991 ((f - iv[MIN] ) <? 0);
994 concaveness2 = concave / (stems.size () - 2);
996 /* ugh: this is the a kludge to get
997 input/regression/beam-concave.ly to behave as
1001 huh? we're dividing twice (which is not scalable) meaning that
1002 the longer the beam, the more unlikely it will be
1003 concave. Maybe you would even expect the other way around??
1008 concaveness2 /= (stems.size () - 2);
1011 /* TODO: some sort of damping iso -> plain horizontal */
1012 if (concaveness1 || concaveness2 > r2)
1014 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1015 Real r = pos.linear_combination (0);
1016 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1017 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1020 return SCM_UNSPECIFIED;
1023 /* This neat trick is by Werner Lemberg,
1024 damped = tanh (slope)
1025 corresponds with some tables in [Wanske] CHECKME */
1026 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1028 Beam::slope_damping (SCM smob)
1030 Grob *me = unsmob_grob (smob);
1032 if (visible_stem_count (me) <= 1)
1033 return SCM_UNSPECIFIED;
1035 SCM s = me->get_grob_property ("damping");
1036 int damping = gh_scm2int (s);
1040 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1041 Real dy = pos.delta ();
1043 Grob *fvs = first_visible_stem (me);
1044 Grob *lvs = last_visible_stem (me);
1046 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1049 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1050 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1051 Real dydx = dy && dx ? dy/dx : 0;
1052 dydx = 0.6 * tanh (dydx) / damping;
1054 Real damped_dy = dydx * dx;
1055 pos[LEFT] += (dy - damped_dy) / 2;
1056 pos[RIGHT] -= (dy - damped_dy) / 2;
1058 me->set_grob_property ("positions", ly_interval2scm (pos));
1060 return SCM_UNSPECIFIED;
1064 Report slice containing the numbers that are both in (car BEAMING)
1068 where_are_the_whole_beams(SCM beaming)
1072 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1074 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1076 l.add_point (gh_scm2int (gh_car (s)));
1083 Calculate the Y position of the stem-end, given the Y-left, Y-right
1084 in POS for stem S. This Y position is relative to S.
1087 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1089 Interval pos, bool french)
1091 Real beam_translation = get_beam_translation (me);
1094 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1095 Real dy = pos.delta ();
1097 Real stem_y_beam0 = (dy && dx
1102 Direction my_dir = Directional_element_interface::get (s);
1103 SCM beaming = s->get_grob_property ("beaming");
1105 Real stem_y = stem_y_beam0;
1108 Slice bm = where_are_the_whole_beams (beaming);
1110 stem_y += beam_translation * bm[-my_dir];
1114 Slice bm = Stem::beam_multiplicity(s);
1116 stem_y +=bm[my_dir] * beam_translation;
1119 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1120 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1126 Hmm. At this time, beam position and slope are determined. Maybe,
1127 stem directions and length should set to relative to the chord's
1128 position of the beam. */
1130 Beam::set_stem_lengths (Grob *me)
1132 Link_array<Grob> stems=
1133 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1135 if (stems.size () <= 1)
1139 for (int a = 2; a--;)
1140 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1142 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1143 Real staff_space = Staff_symbol_referencer::staff_space (me);
1145 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1150 if (gh_number_p (me->get_grob_property ("gap"))
1151 &&gh_scm2double (me->get_grob_property ("gap")))
1154 thick = gh_scm2double (me->get_grob_property ("thickness"))
1155 * Staff_symbol_referencer::staff_space(me);
1158 // ugh -> use commonx
1159 Grob * fvs = first_visible_stem (me);
1160 Grob *lvs = last_visible_stem (me);
1162 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1163 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1165 for (int i=0; i < stems.size (); i++)
1168 if (Stem::invisible_b (s))
1171 Real stem_y = calc_stem_y (me, s, common,
1173 pos, french && i > 0&& (i < stems.size () -1));
1176 Make the stems go up to the end of the beam. This doesn't matter
1177 for normal beams, but for tremolo beams it looks silly otherwise.
1180 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1182 Stem::set_stemend (s, 2* stem_y / staff_space);
1187 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1189 Link_array<Grob> stems=
1190 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1193 for (int i=0; i < stems.size (); i++)
1196 Don't overwrite user settings.
1201 /* Don't set beaming for outside of outer stems */
1202 if ((d == LEFT && i == 0)
1203 ||(d == RIGHT && i == stems.size () -1))
1207 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1208 if (beaming_prop == SCM_EOL ||
1209 index_get_cell (beaming_prop, d) == SCM_EOL)
1211 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1212 Stem::set_beaming (stems[i], b, d);
1215 while (flip (&d) != LEFT);
1220 Beam::forced_stem_count (Grob *me)
1222 Link_array<Grob>stems =
1223 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1225 for (int i=0; i < stems.size (); i++)
1229 if (Stem::invisible_b (s))
1232 if (((int)Stem::chord_start_y (s))
1233 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1243 Beam::visible_stem_count (Grob *me)
1245 Link_array<Grob>stems =
1246 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1248 for (int i = stems.size (); i--;)
1250 if (!Stem::invisible_b (stems[i]))
1257 Beam::first_visible_stem (Grob *me)
1259 Link_array<Grob>stems =
1260 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1262 for (int i = 0; i < stems.size (); i++)
1264 if (!Stem::invisible_b (stems[i]))
1271 Beam::last_visible_stem (Grob *me)
1273 Link_array<Grob>stems =
1274 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1275 for (int i = stems.size (); i--;)
1277 if (!Stem::invisible_b (stems[i]))
1287 handle rest under beam (do_post: beams are calculated now)
1288 what about combination of collisions and rest under beam.
1292 rest -> stem -> beam -> interpolate_y_position ()
1294 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1296 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1298 Grob *rest = unsmob_grob (element_smob);
1299 Axis a = (Axis) gh_scm2int (axis);
1301 assert (a == Y_AXIS);
1303 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1306 return gh_double2scm (0.0);
1307 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1309 || !Beam::has_interface (beam)
1310 || !Beam::visible_stem_count (beam))
1311 return gh_double2scm (0.0);
1313 // make callback for rest from this.
1314 // todo: make sure this calced already.
1316 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1317 Interval pos (0, 0);
1318 SCM s = beam->get_grob_property ("positions");
1319 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1320 pos = ly_scm2interval (s);
1322 Real dy = pos.delta ();
1323 // ugh -> use commonx
1324 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1325 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1326 Real dydx = dy && dx ? dy/dx : 0;
1328 Direction d = Stem::get_direction (stem);
1329 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1331 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1334 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1337 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1339 minimum_dist + -d * (beamy - rest_dim) >? 0;
1341 int stafflines = Staff_symbol_referencer::line_count (rest);
1343 // move discretely by half spaces.
1344 int discrete_dist = int (ceil (dist));
1346 // move by whole spaces inside the staff.
1347 if (discrete_dist < stafflines+1)
1348 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1350 return gh_double2scm (-d * discrete_dist);
1354 Beam::knee_b (Grob*me)
1356 SCM k = me->get_grob_property ("knee");
1357 if (gh_boolean_p (k))
1358 return gh_scm2bool (k);
1362 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1364 Direction dir = Directional_element_interface::get
1365 (unsmob_grob (ly_car (s)));
1374 me->set_grob_property ("knee", gh_bool2scm (knee));
1379 ADD_INTERFACE (Beam, "beam-interface",
1382 #'thickness= weight of beams, in staffspace
1385 We take the least squares line through the ideal-length stems, and
1386 then damp that using
1388 damped = tanh (slope)
1390 this gives an unquantized left and right position for the beam end.
1391 Then we take all combinations of quantings near these left and right
1392 positions, and give them a score (according to how close they are to
1393 the ideal slope, how close the result is to the ideal stems, etc.). We
1394 take the best scoring combination.
1397 "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");