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 (), scm_int2num (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);
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 (scm_int2num (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, scm_int2num (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, scm_int2num (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, scm_int2num (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, scm_makfrom0str (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<Grob> stems=
498 Pointer_group_interface__extract_grobs (me, (Grob*)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 (scm_int2num (count[UP]),
518 scm_int2num (count[DOWN])),
519 gh_cons (scm_int2num (total[UP]),
520 scm_int2num (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<Grob> stems
537 =Pointer_group_interface__extract_grobs (me, (Grob*) 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);
563 A union of intervals in the real line.
565 Abysmal performance (quadratic) for large N, hopefully we don't have
566 that large N. In any case, this should probably be rewritten to use
571 Array<Interval> allowed_regions_;
580 allowed_regions_.clear();
583 allowed_regions_.push (s);
586 void remove_interval (Interval rm)
588 for (int i = 0; i < allowed_regions_.size(); )
592 s.intersect (allowed_regions_[i]);
596 Interval before = allowed_regions_[i];
597 Interval after = allowed_regions_[i];
599 before[RIGHT] = s[LEFT];
600 after[LEFT] = s[RIGHT];
602 if (!before.empty_b() && before.length () > 0.0)
604 allowed_regions_.insert (before, i);
607 allowed_regions_.del (i);
608 if (!after.empty_b () && after.length () > 0.0)
610 allowed_regions_.insert (after, i);
622 Only try horizontal beams for knees. No reliable detection of
623 anything else is possible here, since we don't know funky-beaming
624 settings, or X-distances (slopes!) People that want sloped
625 knee-beams, should set the directions manually.
628 Beam::consider_auto_knees (Grob* me)
630 SCM scm = me->get_grob_property ("auto-knee-gap");
631 if (!gh_number_p (scm))
634 Real threshold = gh_scm2double (scm);
640 Link_array<Grob> stems=
641 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
643 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
644 Real staff_space = Staff_symbol_referencer::staff_space (me);
646 Array<Interval> hps_array;
647 for (int i=0; i < stems.size (); i++)
649 Grob* stem = stems[i];
650 if (Stem::invisible_b (stem))
654 Interval hps = Stem::head_positions (stem);
660 hps *= staff_space * 0.5 ;
661 hps += stem->relative_coordinate (common, Y_AXIS);
663 if (to_boolean (stem->get_grob_property ("dir-forced")))
665 Direction stemdir =Directional_element_interface::get (stem);
666 hps[-stemdir] = - stemdir * infinity_f;
669 hps_array.push (hps);
671 gaps.remove_interval (hps);
675 Real max_gap_len =0.0;
677 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
679 Interval gap = gaps.allowed_regions_[i];
682 the outer gaps are not knees.
684 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
687 if (gap.length () >= max_gap_len)
689 max_gap_len = gap.length();
694 if (max_gap_len > threshold)
697 for (int i = 0; i < stems.size(); i++)
699 Grob* stem = stems[i];
700 if (Stem::invisible_b (stem))
703 Interval hps = hps_array[j++];
706 Direction d = (hps.center () < max_gap.center()) ?
709 stem->set_grob_property ("direction", scm_int2num (d));
712 UGH. Check why we still need dir-forced; I think we can
715 stem->set_grob_property ("dir-forced", SCM_BOOL_T);
717 hps.intersect (max_gap);
718 assert (hps.empty_b () || hps.length () < 1e-6 );
725 /* Set stem's shorten property if unset.
728 take some y-position (chord/beam/nearest?) into account
729 scmify forced-fraction
731 This is done in beam because the shorten has to be uniform over the
736 Beam::set_stem_shorten (Grob *me)
739 shortening looks silly for x staff beams
744 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
746 int beam_count = get_beam_count (me);
748 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
749 if (shorten_list == SCM_EOL)
752 Real staff_space = Staff_symbol_referencer::staff_space (me);
755 robust_list_ref (beam_count -1, shorten_list);
756 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
758 /* your similar cute comment here */
759 shorten_f *= forced_fraction;
762 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
765 /* Call list of y-dy-callbacks, that handle setting of
769 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
771 Beam::after_line_breaking (SCM smob)
773 Grob *me = unsmob_grob (smob);
775 /* Copy to mutable list. */
776 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
777 me->set_grob_property ("positions", s);
779 if (ly_car (s) == SCM_BOOL_F)
782 // one wonders if such genericity is necessary --hwn.
783 SCM callbacks = me->get_grob_property ("position-callbacks");
784 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
785 gh_call1 (ly_car (i), smob);
788 set_stem_lengths (me);
789 return SCM_UNSPECIFIED;
792 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
794 Beam::least_squares (SCM smob)
796 Grob *me = unsmob_grob (smob);
798 int count = visible_stem_count (me);
803 me->set_grob_property ("positions", ly_interval2scm (pos));
804 return SCM_UNSPECIFIED;
808 Array<Real> x_posns ;
809 Link_array<Grob> stems=
810 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
811 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
812 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
814 Real my_y = me->relative_coordinate (commony, Y_AXIS);
816 Grob *fvs = first_visible_stem (me);
817 Grob *lvs = last_visible_stem (me);
819 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
820 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
821 Stem::calc_stem_info (lvs).ideal_y_
822 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
824 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
825 for (int i=0; i < stems.size (); i++)
829 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
832 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
840 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
841 Stem::chord_start_y (last_visible_stem (me)));
845 TODO -- use scoring for this.
847 complicated, because we take stem-info.ideal for determining
850 /* Make simple beam on middle line have small tilt */
851 if (!ideal[LEFT] && chord.delta () && count == 2)
857 Direction d = (Direction) (sign (chord.delta ()) * UP);
858 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
873 Array<Offset> ideals;
874 for (int i=0; i < stems.size (); i++)
877 if (Stem::invisible_b (s))
879 ideals.push (Offset (x_posns[i],
880 Stem::calc_stem_info (s).ideal_y_
881 + s->relative_coordinate (commony, Y_AXIS)
884 minimise_least_squares (&dydx, &y, ideals);
887 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
888 pos = Interval (y, (y+dy));
891 me->set_grob_property ("positions", ly_interval2scm (pos));
893 return SCM_UNSPECIFIED;
898 We can't combine with previous function, since check concave and
899 slope damping comes first.
901 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
903 Beam::shift_region_to_valid (SCM grob)
905 Grob *me = unsmob_grob (grob);
909 Array<Real> x_posns ;
910 Link_array<Grob> stems=
911 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
912 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
913 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
915 Grob *fvs = first_visible_stem (me);
918 return SCM_UNSPECIFIED;
920 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
921 for (int i=0; i < stems.size (); i++)
925 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
929 Grob *lvs = last_visible_stem (me);
931 return SCM_UNSPECIFIED;
933 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
935 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
936 Real dy = pos.delta();
942 Shift the positions so that we have a chance of finding good
943 quants (i.e. no short stem failures.)
945 Interval feasible_left_point;
946 feasible_left_point.set_full ();
947 for (int i=0; i < stems.size (); i++)
950 if (Stem::invisible_b (s))
953 Direction d = Stem::get_direction (s);
956 Stem::calc_stem_info (s).shortest_y_
957 - dydx * x_posns [i];
960 left_y is now relative to the stem S. We want relative to
961 ourselves, so translate:
964 + s->relative_coordinate (commony, Y_AXIS)
965 - me->relative_coordinate (commony, Y_AXIS);
971 feasible_left_point.intersect (flp);
974 if (feasible_left_point.empty_b())
976 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
978 else if (!feasible_left_point.elem_b(y))
980 if (isinf (feasible_left_point[DOWN]))
981 y = feasible_left_point[UP] - REGION_SIZE;
982 else if (isinf (feasible_left_point[UP]))
983 y = feasible_left_point[DOWN]+ REGION_SIZE;
985 y = feasible_left_point.center ();
987 pos = Interval (y, (y+dy));
988 me->set_grob_property ("positions", ly_interval2scm (pos));
989 return SCM_UNSPECIFIED;
993 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
995 Beam::check_concave (SCM smob)
997 Grob *me = unsmob_grob (smob);
999 Link_array<Grob> stems =
1000 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1002 for (int i = 0; i < stems.size ();)
1004 if (Stem::invisible_b (stems[i]))
1010 if (stems.size () < 3)
1011 return SCM_UNSPECIFIED;
1014 /* Concaveness #1: If distance of an inner notehead to line between
1015 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1016 beam is concave (Heinz Stolba).
1018 In the case of knees, the line connecting outer heads is often
1019 not related to the beam slope (it may even go in the other
1020 direction). Skip the check when the outer stems point in
1021 different directions. --hwn
1024 bool concaveness1 = false;
1025 SCM gap = me->get_grob_property ("concaveness-gap");
1026 if (gh_number_p (gap)
1027 && Stem::get_direction(stems.top ())
1028 == Stem::get_direction(stems[0]))
1030 Real r1 = gh_scm2double (gap);
1031 Real dy = Stem::chord_start_y (stems.top ())
1032 - Stem::chord_start_y (stems[0]);
1035 Real slope = dy / (stems.size () - 1);
1037 Real y0 = Stem::chord_start_y (stems[0]);
1038 for (int i = 1; i < stems.size () - 1; i++)
1040 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1043 concaveness1 = true;
1050 /* Concaveness #2: Sum distances of inner noteheads that fall
1051 outside the interval of the two outer noteheads.
1053 We only do this for beams where first and last stem have the same
1057 Note that "convex" stems compensate for "concave" stems.
1058 (is that intentional?) --hwn.
1061 Real concaveness2 = 0;
1062 SCM thresh = me->get_grob_property ("concaveness-threshold");
1063 Real r2 = infinity_f;
1064 if (!concaveness1 && gh_number_p (thresh)
1065 && Stem::get_direction(stems.top ())
1066 == Stem::get_direction(stems[0]))
1068 r2 = gh_scm2double (thresh);
1070 Direction dir = Stem::get_direction(stems.top ());
1072 Interval iv (Stem::chord_start_y (stems[0]),
1073 Stem::chord_start_y (stems.top ()));
1075 if (iv[MAX] < iv[MIN])
1078 for (int i = 1; i < stems.size () - 1; i++)
1080 Real f = Stem::chord_start_y (stems[i]);
1081 concave += ((f - iv[MAX] ) >? 0) +
1082 ((f - iv[MIN] ) <? 0);
1085 concaveness2 = concave / (stems.size () - 2);
1087 /* ugh: this is the a kludge to get
1088 input/regression/beam-concave.ly to behave as
1092 huh? we're dividing twice (which is not scalable) meaning that
1093 the longer the beam, the more unlikely it will be
1094 concave. Maybe you would even expect the other way around??
1099 concaveness2 /= (stems.size () - 2);
1102 /* TODO: some sort of damping iso -> plain horizontal */
1103 if (concaveness1 || concaveness2 > r2)
1105 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1106 Real r = pos.linear_combination (0);
1107 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1108 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1111 return SCM_UNSPECIFIED;
1114 /* This neat trick is by Werner Lemberg,
1115 damped = tanh (slope)
1116 corresponds with some tables in [Wanske] CHECKME */
1117 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1119 Beam::slope_damping (SCM smob)
1121 Grob *me = unsmob_grob (smob);
1123 if (visible_stem_count (me) <= 1)
1124 return SCM_UNSPECIFIED;
1126 SCM s = me->get_grob_property ("damping");
1127 int damping = gh_scm2int (s);
1131 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1132 Real dy = pos.delta ();
1134 Grob *fvs = first_visible_stem (me);
1135 Grob *lvs = last_visible_stem (me);
1137 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1140 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1141 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1142 Real dydx = dy && dx ? dy/dx : 0;
1143 dydx = 0.6 * tanh (dydx) / damping;
1145 Real damped_dy = dydx * dx;
1146 pos[LEFT] += (dy - damped_dy) / 2;
1147 pos[RIGHT] -= (dy - damped_dy) / 2;
1149 me->set_grob_property ("positions", ly_interval2scm (pos));
1151 return SCM_UNSPECIFIED;
1155 Report slice containing the numbers that are both in (car BEAMING)
1159 where_are_the_whole_beams(SCM beaming)
1163 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1165 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1167 l.add_point (gh_scm2int (gh_car (s)));
1174 Calculate the Y position of the stem-end, given the Y-left, Y-right
1175 in POS for stem S. This Y position is relative to S.
1178 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1180 Interval pos, bool french)
1182 Real beam_translation = get_beam_translation (me);
1185 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1186 Real dy = pos.delta ();
1188 Real stem_y_beam0 = (dy && dx
1193 Direction my_dir = Directional_element_interface::get (s);
1194 SCM beaming = s->get_grob_property ("beaming");
1196 Real stem_y = stem_y_beam0;
1199 Slice bm = where_are_the_whole_beams (beaming);
1201 stem_y += beam_translation * bm[-my_dir];
1205 Slice bm = Stem::beam_multiplicity(s);
1207 stem_y +=bm[my_dir] * beam_translation;
1210 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1211 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1217 Hmm. At this time, beam position and slope are determined. Maybe,
1218 stem directions and length should set to relative to the chord's
1219 position of the beam. */
1221 Beam::set_stem_lengths (Grob *me)
1223 Link_array<Grob> stems=
1224 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1226 if (stems.size () <= 1)
1230 for (int a = 2; a--;)
1231 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1233 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1234 Real staff_space = Staff_symbol_referencer::staff_space (me);
1236 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1241 if (gh_number_p (me->get_grob_property ("gap"))
1242 &&gh_scm2double (me->get_grob_property ("gap")))
1245 thick = gh_scm2double (me->get_grob_property ("thickness"))
1246 * Staff_symbol_referencer::staff_space(me);
1249 // ugh -> use commonx
1250 Grob * fvs = first_visible_stem (me);
1251 Grob *lvs = last_visible_stem (me);
1253 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1254 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1256 for (int i=0; i < stems.size (); i++)
1259 if (Stem::invisible_b (s))
1262 Real stem_y = calc_stem_y (me, s, common,
1264 pos, french && i > 0&& (i < stems.size () -1));
1267 Make the stems go up to the end of the beam. This doesn't matter
1268 for normal beams, but for tremolo beams it looks silly otherwise.
1271 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1273 Stem::set_stemend (s, 2* stem_y / staff_space);
1278 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1280 Link_array<Grob> stems=
1281 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1284 for (int i=0; i < stems.size (); i++)
1287 Don't overwrite user settings.
1292 /* Don't set beaming for outside of outer stems */
1293 if ((d == LEFT && i == 0)
1294 ||(d == RIGHT && i == stems.size () -1))
1298 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1299 if (beaming_prop == SCM_EOL ||
1300 index_get_cell (beaming_prop, d) == SCM_EOL)
1302 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1303 Stem::set_beaming (stems[i], b, d);
1306 while (flip (&d) != LEFT);
1311 Beam::forced_stem_count (Grob *me)
1313 Link_array<Grob>stems =
1314 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1316 for (int i=0; i < stems.size (); i++)
1320 if (Stem::invisible_b (s))
1323 if (((int)Stem::chord_start_y (s))
1324 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1334 Beam::visible_stem_count (Grob *me)
1336 Link_array<Grob>stems =
1337 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1339 for (int i = stems.size (); i--;)
1341 if (!Stem::invisible_b (stems[i]))
1348 Beam::first_visible_stem (Grob *me)
1350 Link_array<Grob>stems =
1351 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1353 for (int i = 0; i < stems.size (); i++)
1355 if (!Stem::invisible_b (stems[i]))
1362 Beam::last_visible_stem (Grob *me)
1364 Link_array<Grob>stems =
1365 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1366 for (int i = stems.size (); i--;)
1368 if (!Stem::invisible_b (stems[i]))
1378 handle rest under beam (do_post: beams are calculated now)
1379 what about combination of collisions and rest under beam.
1383 rest -> stem -> beam -> interpolate_y_position ()
1385 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1387 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1389 Grob *rest = unsmob_grob (element_smob);
1390 Axis a = (Axis) gh_scm2int (axis);
1392 assert (a == Y_AXIS);
1394 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1397 return gh_double2scm (0.0);
1398 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1400 || !Beam::has_interface (beam)
1401 || !Beam::visible_stem_count (beam))
1402 return gh_double2scm (0.0);
1404 // make callback for rest from this.
1405 // todo: make sure this calced already.
1407 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1408 Interval pos (0, 0);
1409 SCM s = beam->get_grob_property ("positions");
1410 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1411 pos = ly_scm2interval (s);
1413 Real dy = pos.delta ();
1414 // ugh -> use commonx
1415 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1416 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1417 Real dydx = dy && dx ? dy/dx : 0;
1419 Direction d = Stem::get_direction (stem);
1420 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1422 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1425 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1428 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1430 minimum_dist + -d * (beamy - rest_dim) >? 0;
1432 int stafflines = Staff_symbol_referencer::line_count (rest);
1434 // move discretely by half spaces.
1435 int discrete_dist = int (ceil (dist));
1437 // move by whole spaces inside the staff.
1438 if (discrete_dist < stafflines+1)
1439 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1441 return gh_double2scm (-d * discrete_dist);
1445 Beam::knee_b (Grob* me)
1447 SCM k = me->get_grob_property ("knee");
1448 if (gh_boolean_p (k))
1449 return gh_scm2bool (k);
1453 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1455 Direction dir = Directional_element_interface::get
1456 (unsmob_grob (ly_car (s)));
1465 me->set_grob_property ("knee", gh_bool2scm (knee));
1471 Beam::get_direction_beam_count (Grob *me, Direction d )
1473 Link_array<Grob>stems =
1474 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1477 for (int i = stems.size (); i--;)
1480 Should we take invisible stems into account?
1482 if (Stem::get_direction (stems[i]) == d)
1483 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1490 ADD_INTERFACE (Beam, "beam-interface",
1493 #'thickness= weight of beams, in staffspace
1496 We take the least squares line through the ideal-length stems, and
1497 then damp that using
1499 damped = tanh (slope)
1501 this gives an unquantized left and right position for the beam end.
1502 Then we take all combinations of quantings near these left and right
1503 positions, and give them a score (according to how close they are to
1504 the ideal slope, how close the result is to the ideal stems, etc.). We
1505 take the best scoring combination.
1508 "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");