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 = me->get_grob_property ("beamed-stem-shorten");
749 if (shorten == SCM_EOL)
752 int sz = scm_ilength (shorten);
754 Real staff_space = Staff_symbol_referencer::staff_space (me);
755 SCM shorten_elt = scm_list_ref (shorten,
756 scm_int2num (beam_count <? (sz - 1)));
757 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
759 /* your similar cute comment here */
760 shorten_f *= forced_fraction;
763 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
766 /* Call list of y-dy-callbacks, that handle setting of
770 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
772 Beam::after_line_breaking (SCM smob)
774 Grob *me = unsmob_grob (smob);
776 /* Copy to mutable list. */
777 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
778 me->set_grob_property ("positions", s);
780 if (ly_car (s) == SCM_BOOL_F)
783 // one wonders if such genericity is necessary --hwn.
784 SCM callbacks = me->get_grob_property ("position-callbacks");
785 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
786 gh_call1 (ly_car (i), smob);
789 set_stem_lengths (me);
790 return SCM_UNSPECIFIED;
793 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
795 Beam::least_squares (SCM smob)
797 Grob *me = unsmob_grob (smob);
799 int count = visible_stem_count (me);
804 me->set_grob_property ("positions", ly_interval2scm (pos));
805 return SCM_UNSPECIFIED;
809 Array<Real> x_posns ;
810 Link_array<Grob> stems=
811 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
812 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
813 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
815 Real my_y = me->relative_coordinate (commony, Y_AXIS);
817 Grob *fvs = first_visible_stem (me);
818 Grob *lvs = last_visible_stem (me);
820 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
821 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
822 Stem::calc_stem_info (lvs).ideal_y_
823 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
825 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
826 for (int i=0; i < stems.size (); i++)
830 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
833 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
841 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
842 Stem::chord_start_y (last_visible_stem (me)));
846 TODO -- use scoring for this.
848 complicated, because we take stem-info.ideal for determining
851 /* Make simple beam on middle line have small tilt */
852 if (!ideal[LEFT] && chord.delta () && count == 2)
858 Direction d = (Direction) (sign (chord.delta ()) * UP);
859 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
874 Array<Offset> ideals;
875 for (int i=0; i < stems.size (); i++)
878 if (Stem::invisible_b (s))
880 ideals.push (Offset (x_posns[i],
881 Stem::calc_stem_info (s).ideal_y_
882 + s->relative_coordinate (commony, Y_AXIS)
885 minimise_least_squares (&dydx, &y, ideals);
888 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
889 pos = Interval (y, (y+dy));
892 me->set_grob_property ("positions", ly_interval2scm (pos));
894 return SCM_UNSPECIFIED;
899 We can't combine with previous function, since check concave and
900 slope damping comes first.
902 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
904 Beam::shift_region_to_valid (SCM grob)
906 Grob *me = unsmob_grob (grob);
910 Array<Real> x_posns ;
911 Link_array<Grob> stems=
912 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
913 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
914 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
916 Grob *fvs = first_visible_stem (me);
919 return SCM_UNSPECIFIED;
921 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
922 for (int i=0; i < stems.size (); i++)
926 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
930 Grob *lvs = last_visible_stem (me);
932 return SCM_UNSPECIFIED;
934 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
936 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
937 Real dy = pos.delta();
943 Shift the positions so that we have a chance of finding good
944 quants (i.e. no short stem failures.)
946 Interval feasible_left_point;
947 feasible_left_point.set_full ();
948 for (int i=0; i < stems.size (); i++)
951 if (Stem::invisible_b (s))
954 Direction d = Stem::get_direction (s);
957 Stem::calc_stem_info (s).shortest_y_
958 - dydx * x_posns [i];
961 left_y is now relative to the stem S. We want relative to
962 ourselves, so translate:
965 + s->relative_coordinate (commony, Y_AXIS)
966 - me->relative_coordinate (commony, Y_AXIS);
972 feasible_left_point.intersect (flp);
975 if (feasible_left_point.empty_b())
977 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
979 else if (!feasible_left_point.elem_b(y))
981 if (isinf (feasible_left_point[DOWN]))
982 y = feasible_left_point[UP] - REGION_SIZE;
983 else if (isinf (feasible_left_point[UP]))
984 y = feasible_left_point[DOWN]+ REGION_SIZE;
986 y = feasible_left_point.center ();
988 pos = Interval (y, (y+dy));
989 me->set_grob_property ("positions", ly_interval2scm (pos));
990 return SCM_UNSPECIFIED;
994 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
996 Beam::check_concave (SCM smob)
998 Grob *me = unsmob_grob (smob);
1000 Link_array<Grob> stems =
1001 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1003 for (int i = 0; i < stems.size ();)
1005 if (Stem::invisible_b (stems[i]))
1011 if (stems.size () < 3)
1012 return SCM_UNSPECIFIED;
1015 /* Concaveness #1: If distance of an inner notehead to line between
1016 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1017 beam is concave (Heinz Stolba).
1019 In the case of knees, the line connecting outer heads is often
1020 not related to the beam slope (it may even go in the other
1021 direction). Skip the check when the outer stems point in
1022 different directions. --hwn
1025 bool concaveness1 = false;
1026 SCM gap = me->get_grob_property ("concaveness-gap");
1027 if (gh_number_p (gap)
1028 && Stem::get_direction(stems.top ())
1029 == Stem::get_direction(stems[0]))
1031 Real r1 = gh_scm2double (gap);
1032 Real dy = Stem::chord_start_y (stems.top ())
1033 - Stem::chord_start_y (stems[0]);
1036 Real slope = dy / (stems.size () - 1);
1038 Real y0 = Stem::chord_start_y (stems[0]);
1039 for (int i = 1; i < stems.size () - 1; i++)
1041 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1044 concaveness1 = true;
1051 /* Concaveness #2: Sum distances of inner noteheads that fall
1052 outside the interval of the two outer noteheads.
1054 We only do this for beams where first and last stem have the same
1058 Note that "convex" stems compensate for "concave" stems.
1059 (is that intentional?) --hwn.
1062 Real concaveness2 = 0;
1063 SCM thresh = me->get_grob_property ("concaveness-threshold");
1064 Real r2 = infinity_f;
1065 if (!concaveness1 && gh_number_p (thresh)
1066 && Stem::get_direction(stems.top ())
1067 == Stem::get_direction(stems[0]))
1069 r2 = gh_scm2double (thresh);
1071 Direction dir = Stem::get_direction(stems.top ());
1073 Interval iv (Stem::chord_start_y (stems[0]),
1074 Stem::chord_start_y (stems.top ()));
1076 if (iv[MAX] < iv[MIN])
1079 for (int i = 1; i < stems.size () - 1; i++)
1081 Real f = Stem::chord_start_y (stems[i]);
1082 concave += ((f - iv[MAX] ) >? 0) +
1083 ((f - iv[MIN] ) <? 0);
1086 concaveness2 = concave / (stems.size () - 2);
1088 /* ugh: this is the a kludge to get
1089 input/regression/beam-concave.ly to behave as
1093 huh? we're dividing twice (which is not scalable) meaning that
1094 the longer the beam, the more unlikely it will be
1095 concave. Maybe you would even expect the other way around??
1100 concaveness2 /= (stems.size () - 2);
1103 /* TODO: some sort of damping iso -> plain horizontal */
1104 if (concaveness1 || concaveness2 > r2)
1106 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1107 Real r = pos.linear_combination (0);
1108 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1109 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1112 return SCM_UNSPECIFIED;
1115 /* This neat trick is by Werner Lemberg,
1116 damped = tanh (slope)
1117 corresponds with some tables in [Wanske] CHECKME */
1118 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1120 Beam::slope_damping (SCM smob)
1122 Grob *me = unsmob_grob (smob);
1124 if (visible_stem_count (me) <= 1)
1125 return SCM_UNSPECIFIED;
1127 SCM s = me->get_grob_property ("damping");
1128 int damping = gh_scm2int (s);
1132 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1133 Real dy = pos.delta ();
1135 Grob *fvs = first_visible_stem (me);
1136 Grob *lvs = last_visible_stem (me);
1138 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1141 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1142 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1143 Real dydx = dy && dx ? dy/dx : 0;
1144 dydx = 0.6 * tanh (dydx) / damping;
1146 Real damped_dy = dydx * dx;
1147 pos[LEFT] += (dy - damped_dy) / 2;
1148 pos[RIGHT] -= (dy - damped_dy) / 2;
1150 me->set_grob_property ("positions", ly_interval2scm (pos));
1152 return SCM_UNSPECIFIED;
1156 Report slice containing the numbers that are both in (car BEAMING)
1160 where_are_the_whole_beams(SCM beaming)
1164 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1166 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1168 l.add_point (gh_scm2int (gh_car (s)));
1175 Calculate the Y position of the stem-end, given the Y-left, Y-right
1176 in POS for stem S. This Y position is relative to S.
1179 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1181 Interval pos, bool french)
1183 Real beam_translation = get_beam_translation (me);
1186 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1187 Real dy = pos.delta ();
1189 Real stem_y_beam0 = (dy && dx
1194 Direction my_dir = Directional_element_interface::get (s);
1195 SCM beaming = s->get_grob_property ("beaming");
1197 Real stem_y = stem_y_beam0;
1200 Slice bm = where_are_the_whole_beams (beaming);
1202 stem_y += beam_translation * bm[-my_dir];
1206 Slice bm = Stem::beam_multiplicity(s);
1208 stem_y +=bm[my_dir] * beam_translation;
1211 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1212 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1218 Hmm. At this time, beam position and slope are determined. Maybe,
1219 stem directions and length should set to relative to the chord's
1220 position of the beam. */
1222 Beam::set_stem_lengths (Grob *me)
1224 Link_array<Grob> stems=
1225 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1227 if (stems.size () <= 1)
1231 for (int a = 2; a--;)
1232 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1234 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1235 Real staff_space = Staff_symbol_referencer::staff_space (me);
1237 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1242 if (gh_number_p (me->get_grob_property ("gap"))
1243 &&gh_scm2double (me->get_grob_property ("gap")))
1246 thick = gh_scm2double (me->get_grob_property ("thickness"))
1247 * Staff_symbol_referencer::staff_space(me);
1250 // ugh -> use commonx
1251 Grob * fvs = first_visible_stem (me);
1252 Grob *lvs = last_visible_stem (me);
1254 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1255 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1257 for (int i=0; i < stems.size (); i++)
1260 if (Stem::invisible_b (s))
1263 Real stem_y = calc_stem_y (me, s, common,
1265 pos, french && i > 0&& (i < stems.size () -1));
1268 Make the stems go up to the end of the beam. This doesn't matter
1269 for normal beams, but for tremolo beams it looks silly otherwise.
1272 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1274 Stem::set_stemend (s, 2* stem_y / staff_space);
1279 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1281 Link_array<Grob> stems=
1282 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1285 for (int i=0; i < stems.size (); i++)
1288 Don't overwrite user settings.
1293 /* Don't set beaming for outside of outer stems */
1294 if ((d == LEFT && i == 0)
1295 ||(d == RIGHT && i == stems.size () -1))
1299 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1300 if (beaming_prop == SCM_EOL ||
1301 index_get_cell (beaming_prop, d) == SCM_EOL)
1303 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1304 Stem::set_beaming (stems[i], b, d);
1307 while (flip (&d) != LEFT);
1312 Beam::forced_stem_count (Grob *me)
1314 Link_array<Grob>stems =
1315 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1317 for (int i=0; i < stems.size (); i++)
1321 if (Stem::invisible_b (s))
1324 if (((int)Stem::chord_start_y (s))
1325 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1335 Beam::visible_stem_count (Grob *me)
1337 Link_array<Grob>stems =
1338 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1340 for (int i = stems.size (); i--;)
1342 if (!Stem::invisible_b (stems[i]))
1349 Beam::first_visible_stem (Grob *me)
1351 Link_array<Grob>stems =
1352 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1354 for (int i = 0; i < stems.size (); i++)
1356 if (!Stem::invisible_b (stems[i]))
1363 Beam::last_visible_stem (Grob *me)
1365 Link_array<Grob>stems =
1366 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1367 for (int i = stems.size (); i--;)
1369 if (!Stem::invisible_b (stems[i]))
1379 handle rest under beam (do_post: beams are calculated now)
1380 what about combination of collisions and rest under beam.
1384 rest -> stem -> beam -> interpolate_y_position ()
1386 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1388 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1390 Grob *rest = unsmob_grob (element_smob);
1391 Axis a = (Axis) gh_scm2int (axis);
1393 assert (a == Y_AXIS);
1395 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1398 return gh_double2scm (0.0);
1399 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1401 || !Beam::has_interface (beam)
1402 || !Beam::visible_stem_count (beam))
1403 return gh_double2scm (0.0);
1405 // make callback for rest from this.
1406 // todo: make sure this calced already.
1408 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1409 Interval pos (0, 0);
1410 SCM s = beam->get_grob_property ("positions");
1411 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1412 pos = ly_scm2interval (s);
1414 Real dy = pos.delta ();
1415 // ugh -> use commonx
1416 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1417 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1418 Real dydx = dy && dx ? dy/dx : 0;
1420 Direction d = Stem::get_direction (stem);
1421 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1423 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1426 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1429 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1431 minimum_dist + -d * (beamy - rest_dim) >? 0;
1433 int stafflines = Staff_symbol_referencer::line_count (rest);
1435 // move discretely by half spaces.
1436 int discrete_dist = int (ceil (dist));
1438 // move by whole spaces inside the staff.
1439 if (discrete_dist < stafflines+1)
1440 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1442 return gh_double2scm (-d * discrete_dist);
1446 Beam::knee_b (Grob* me)
1448 SCM k = me->get_grob_property ("knee");
1449 if (gh_boolean_p (k))
1450 return gh_scm2bool (k);
1454 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1456 Direction dir = Directional_element_interface::get
1457 (unsmob_grob (ly_car (s)));
1466 me->set_grob_property ("knee", gh_bool2scm (knee));
1472 Beam::get_direction_beam_count (Grob *me, Direction d )
1474 Link_array<Grob>stems =
1475 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1478 for (int i = stems.size (); i--;)
1481 Should we take invisible stems into account?
1483 if (Stem::get_direction (stems[i]) == d)
1484 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1491 ADD_INTERFACE (Beam, "beam-interface",
1494 #'thickness= weight of beams, in staffspace
1497 We take the least squares line through the ideal-length stems, and
1498 then damp that using
1500 damped = tanh (slope)
1502 this gives an unquantized left and right position for the beam end.
1503 Then we take all combinations of quantings near these left and right
1504 positions, and give them a score (according to how close they are to
1505 the ideal slope, how close the result is to the ideal stems, etc.). We
1506 take the best scoring combination.
1509 "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");