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);
84 /* Maximum beam_count. */
86 Beam::get_beam_count (Grob *me)
89 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
91 Grob *stem = unsmob_grob (ly_car (s));
92 m = m >? (Stem::beam_multiplicity (stem).length () + 1);
97 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
99 Beam::space_function (SCM smob, SCM beam_count)
101 Grob *me = unsmob_grob (smob);
103 Real staff_space = Staff_symbol_referencer::staff_space (me);
104 Real line = me->get_paper ()->get_var ("linethickness");
105 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
108 Real beam_translation = gh_scm2int (beam_count) < 4
109 ? (2*staff_space + line - thickness) / 2.0
110 : (3*staff_space + line - thickness) / 3.0;
112 return gh_double2scm (beam_translation);
116 /* After pre-processing all directions should be set.
117 Several post-processing routines (stem, slur, script) need stem/beam
119 Currenly, this means that beam has set all stem's directions.
120 [Alternatively, stems could set its own directions, according to
121 their beam, during 'final-pre-processing'.] */
122 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
124 Beam::before_line_breaking (SCM smob)
126 Grob *me = unsmob_grob (smob);
128 /* Beams with less than 2 two stems don't make much sense, but could happen
133 For a beam that only has one stem, we try to do some disappearance magic:
134 we revert the flag, and move on to The Eternal Engraving Fields. */
136 int count = visible_stem_count (me);
139 me->warning (_ ("beam has less than two visible stems"));
141 SCM stems = me->get_grob_property ("stems");
142 if (scm_ilength (stems) == 1)
144 me->warning (_ ("Beam has less than two stems. Removing beam."));
146 unsmob_grob (gh_car (stems))->set_grob_property ("beam", SCM_EOL);
149 return SCM_UNSPECIFIED;
151 else if (scm_ilength (stems) == 0)
154 return SCM_UNSPECIFIED;
159 Direction d = get_default_dir (me);
161 consider_auto_knees (me);
162 set_stem_directions (me, d);
166 set_stem_shorten (me);
174 We want a maximal number of shared beams, but if there is choice, we
175 take the one that is closest to the end of the stem. This is for situations like
188 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
192 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
196 for (int i = lslice[-left_dir];
197 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
200 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
202 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
203 if (scm_memq (scm_int2num (k), left_beaming) != SCM_BOOL_F)
207 if (count >= best_count)
218 Beam::connect_beams (Grob *me)
220 Link_array<Grob> stems=
221 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
224 last_int.set_empty();
225 SCM last_beaming = SCM_EOL;
226 Direction last_dir = CENTER;
227 for (int i = 0; i< stems.size(); i++)
229 Grob *this_stem = stems[i];
230 SCM this_beaming = this_stem->get_grob_property ("beaming");
232 Direction this_dir = Directional_element_interface::get(this_stem);
235 int start_point = position_with_maximal_common_beams
236 (last_beaming, this_beaming,
243 if (d == RIGHT && i == stems.size()-1)
246 new_slice.set_empty();
247 SCM s = index_get_cell (this_beaming, d);
248 for (; gh_pair_p (s); s = gh_cdr (s))
251 start_point - this_dir * gh_scm2int (gh_car (s));
253 new_slice.add_point (new_beam_pos);
254 gh_set_car_x (s, scm_int2num (new_beam_pos));
259 while (flip (&d) != LEFT);
261 if (!new_slice.empty_b())
262 last_int = new_slice;
266 gh_set_car_x ( this_beaming, SCM_EOL);
267 SCM s = gh_cdr (this_beaming);
268 for (; gh_pair_p (s); s = gh_cdr (s))
270 int np = - this_dir * gh_scm2int (gh_car(s));
271 gh_set_car_x (s, scm_int2num (np));
272 last_int.add_point (np);
276 if (i == stems.size () -1)
278 gh_set_cdr_x (this_beaming, SCM_EOL);
281 if (scm_ilength (gh_cdr (this_beaming)) > 0)
283 last_beaming = this_beaming;
289 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
291 Beam::brew_molecule (SCM grob)
293 Grob *me = unsmob_grob (grob);
294 Link_array<Grob> stems=
295 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
296 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
299 if (visible_stem_count (me))
301 // ugh -> use commonx
302 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
303 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
307 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
308 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
311 SCM posns = me->get_grob_property ("positions");
313 if (!ly_number_pair_p (posns))
315 programming_error ("No beam posns");
316 pos = Interval (0,0);
319 pos= ly_scm2interval (posns);
321 Real dy = pos.delta ();
322 Real dydx = dy && dx ? dy/dx : 0;
324 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
325 Real bdy = get_beam_translation (me);
327 SCM last_beaming = SCM_EOL;;
328 Real last_xposn = -1;
329 Real last_width = -1 ;
332 SCM gap = me->get_grob_property ("gap");
334 Real lt = me->get_paper ()->get_var ("linethickness");
335 for (int i = 0; i< stems.size(); i++)
339 SCM this_beaming = st->get_grob_property ("beaming");
340 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
341 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
345 SCM left = gh_cdr (last_beaming);
346 SCM right = gh_car (this_beaming);
348 Array<int> fullbeams;
349 Array<int> lfliebertjes;
350 Array<int> rfliebertjes;
353 gh_pair_p (s); s =gh_cdr (s))
355 int b = gh_scm2int (gh_car (s));
356 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
362 lfliebertjes.push (b);
366 gh_pair_p (s); s =gh_cdr (s))
368 int b = gh_scm2int (gh_car (s));
369 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
371 rfliebertjes.push (b);
376 Real w = xposn - last_xposn;
377 Real stem_offset = 0.0;
378 Real width_corr = 0.0;
381 stem_offset -= last_width/2;
382 width_corr += last_width/2;
385 if (i == stems.size() -1)
387 width_corr += stem_width/2;
390 if (gh_number_p (gap))
392 Real g = gh_scm2double (gap);
397 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
398 for (int j = fullbeams.size(); j--;)
401 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
402 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
403 the_beam.add_molecule (b);
406 if (lfliebertjes.size() || rfliebertjes.size())
410 if (!Stem::first_head (st))
414 int t = Stem::duration_log (st);
416 SCM proc = me->get_grob_property ("flag-width-function");
417 SCM result = gh_call1 (proc, scm_int2num (t));
418 nw_f = gh_scm2double (result);
421 /* Half beam should be one note-width,
422 but let's make sure two half-beams never touch */
424 Real w = xposn - last_xposn;
427 Molecule half = Lookup::beam (dydx, w, thick);
428 for (int j = lfliebertjes.size(); j--;)
431 b.translate_axis (last_xposn - x0, X_AXIS);
432 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
433 the_beam.add_molecule (b);
435 for (int j = rfliebertjes.size(); j--;)
438 b.translate_axis (xposn - x0 - w , X_AXIS);
439 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
440 the_beam.add_molecule (b);
446 last_width = stem_width;
447 last_beaming = this_beaming;
450 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
451 the_beam.translate_axis (pos[LEFT], Y_AXIS);
456 This code prints the demerits for each beam. Perhaps this
457 should be switchable for those who want to twiddle with the
463 str += to_string (gh_scm2int (me->get_grob_property ("best-idx")));
466 str += to_string (gh_scm2double (me->get_grob_property ("quant-score")),
469 SCM properties = Font_interface::font_alist_chain (me);
472 Molecule tm = Text_item::text2molecule (me, scm_makfrom0str (str.to_str0 ()), properties);
473 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
479 return the_beam.smobbed_copy();
486 Beam::get_default_dir (Grob *me)
488 Drul_array<int> total;
489 total[UP] = total[DOWN] = 0;
490 Drul_array<int> count;
491 count[UP] = count[DOWN] = 0;
494 Link_array<Grob> stems=
495 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
497 for (int i=0; i <stems.size (); i++)
500 Direction sd = Directional_element_interface::get (s);
502 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
503 int current = sd ? (1 + d * sd)/2 : center_distance;
510 } while (flip (&d) != DOWN);
512 SCM func = me->get_grob_property ("dir-function");
513 SCM s = gh_call2 (func,
514 gh_cons (scm_int2num (count[UP]),
515 scm_int2num (count[DOWN])),
516 gh_cons (scm_int2num (total[UP]),
517 scm_int2num (total[DOWN])));
519 if (gh_number_p (s) && gh_scm2int (s))
522 /* If dir is not determined: get default */
523 return to_dir (me->get_grob_property ("neutral-direction"));
527 /* Set all stems with non-forced direction to beam direction.
528 Urg: non-forced should become `without/with unforced' direction,
529 once stem gets cleaned-up. */
531 Beam::set_stem_directions (Grob *me, Direction d)
533 Link_array<Grob> stems
534 =Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
536 for (int i=0; i <stems.size (); i++)
540 SCM forcedir = s->get_grob_property ("direction");
541 if (!to_dir (forcedir))
542 Directional_element_interface::set (s, d);
547 A union of intervals in the real line.
549 Abysmal performance (quadratic) for large N, hopefully we don't have
550 that large N. In any case, this should probably be rewritten to use
555 Array<Interval> allowed_regions_;
564 allowed_regions_.clear();
567 allowed_regions_.push (s);
570 void remove_interval (Interval rm)
572 for (int i = 0; i < allowed_regions_.size(); )
576 s.intersect (allowed_regions_[i]);
580 Interval before = allowed_regions_[i];
581 Interval after = allowed_regions_[i];
583 before[RIGHT] = s[LEFT];
584 after[LEFT] = s[RIGHT];
586 if (!before.empty_b() && before.length () > 0.0)
588 allowed_regions_.insert (before, i);
591 allowed_regions_.del (i);
592 if (!after.empty_b () && after.length () > 0.0)
594 allowed_regions_.insert (after, i);
606 Only try horizontal beams for knees. No reliable detection of
607 anything else is possible here, since we don't know funky-beaming
608 settings, or X-distances (slopes!) People that want sloped
609 knee-beams, should set the directions manually.
612 Beam::consider_auto_knees (Grob* me)
614 SCM scm = me->get_grob_property ("auto-knee-gap");
615 if (!gh_number_p (scm))
618 Real threshold = gh_scm2double (scm);
624 Link_array<Grob> stems=
625 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
627 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
628 Real staff_space = Staff_symbol_referencer::staff_space (me);
630 Array<Interval> hps_array;
631 for (int i=0; i < stems.size (); i++)
633 Grob* stem = stems[i];
634 if (Stem::invisible_b (stem))
637 Interval hps = Stem::head_positions (stem);
642 hps *= staff_space * 0.5 ;
645 We could subtract beam Y position, but this routine only
646 sets stem directions, a constant shift does not have an
650 hps += stem->relative_coordinate (common, Y_AXIS);
652 if (to_dir (stem->get_grob_property ("direction")))
654 Direction stemdir = to_dir (stem->get_grob_property ("direction"));
655 hps[-stemdir] = - stemdir * infinity_f;
658 hps_array.push (hps);
660 gaps.remove_interval (hps);
664 Real max_gap_len =0.0;
666 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
668 Interval gap = gaps.allowed_regions_[i];
671 the outer gaps are not knees.
673 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
676 if (gap.length () >= max_gap_len)
678 max_gap_len = gap.length();
683 if (max_gap_len > threshold)
686 for (int i = 0; i < stems.size(); i++)
688 Grob* stem = stems[i];
689 if (Stem::invisible_b (stem))
692 Interval hps = hps_array[j++];
695 Direction d = (hps.center () < max_gap.center()) ?
698 stem->set_grob_property ("direction", scm_int2num (d));
700 hps.intersect (max_gap);
701 assert (hps.empty_b () || hps.length () < 1e-6 );
708 /* Set stem's shorten property if unset.
711 take some y-position (chord/beam/nearest?) into account
712 scmify forced-fraction
714 This is done in beam because the shorten has to be uniform over the
719 Beam::set_stem_shorten (Grob *me)
722 shortening looks silly for x staff beams
727 Real forced_fraction = 1.0 * forced_stem_count (me)
728 / visible_stem_count (me);
730 int beam_count = get_beam_count (me);
732 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
733 if (shorten_list == SCM_EOL)
736 Real staff_space = Staff_symbol_referencer::staff_space (me);
739 robust_list_ref (beam_count -1, shorten_list);
740 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
742 /* your similar cute comment here */
743 shorten_f *= forced_fraction;
746 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
749 /* Call list of y-dy-callbacks, that handle setting of
753 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
755 Beam::after_line_breaking (SCM smob)
757 Grob *me = unsmob_grob (smob);
759 /* Copy to mutable list. */
760 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
761 me->set_grob_property ("positions", s);
763 if (ly_car (s) == SCM_BOOL_F)
766 // one wonders if such genericity is necessary --hwn.
767 SCM callbacks = me->get_grob_property ("position-callbacks");
768 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
769 gh_call1 (ly_car (i), smob);
772 set_stem_lengths (me);
773 return SCM_UNSPECIFIED;
776 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
778 Beam::least_squares (SCM smob)
780 Grob *me = unsmob_grob (smob);
782 int count = visible_stem_count (me);
787 me->set_grob_property ("positions", ly_interval2scm (pos));
788 return SCM_UNSPECIFIED;
792 Array<Real> x_posns ;
793 Link_array<Grob> stems=
794 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
795 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
796 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
798 Real my_y = me->relative_coordinate (commony, Y_AXIS);
800 Grob *fvs = first_visible_stem (me);
801 Grob *lvs = last_visible_stem (me);
803 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
804 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
805 Stem::get_stem_info (lvs).ideal_y_
806 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
808 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
809 for (int i=0; i < stems.size (); i++)
813 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
816 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
824 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
825 Stem::chord_start_y (last_visible_stem (me)));
829 TODO -- use scoring for this.
831 complicated, because we take stem-info.ideal for determining
834 /* Make simple beam on middle line have small tilt */
835 if (!ideal[LEFT] && chord.delta () && count == 2)
841 Direction d = (Direction) (sign (chord.delta ()) * UP);
842 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
857 Array<Offset> ideals;
858 for (int i=0; i < stems.size (); i++)
861 if (Stem::invisible_b (s))
863 ideals.push (Offset (x_posns[i],
864 Stem::get_stem_info (s).ideal_y_
865 + s->relative_coordinate (commony, Y_AXIS)
868 minimise_least_squares (&dydx, &y, ideals);
871 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
872 pos = Interval (y, (y+dy));
875 me->set_grob_property ("positions", ly_interval2scm (pos));
877 return SCM_UNSPECIFIED;
882 We can't combine with previous function, since check concave and
883 slope damping comes first.
885 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
887 Beam::shift_region_to_valid (SCM grob)
889 Grob *me = unsmob_grob (grob);
893 Array<Real> x_posns ;
894 Link_array<Grob> stems=
895 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
896 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
897 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
899 Grob *fvs = first_visible_stem (me);
902 return SCM_UNSPECIFIED;
904 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
905 for (int i=0; i < stems.size (); i++)
909 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
913 Grob *lvs = last_visible_stem (me);
915 return SCM_UNSPECIFIED;
917 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
919 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
920 Real dy = pos.delta();
926 Shift the positions so that we have a chance of finding good
927 quants (i.e. no short stem failures.)
929 Interval feasible_left_point;
930 feasible_left_point.set_full ();
931 for (int i=0; i < stems.size (); i++)
934 if (Stem::invisible_b (s))
937 Direction d = Stem::get_direction (s);
940 Stem::get_stem_info (s).shortest_y_
941 - dydx * x_posns [i];
944 left_y is now relative to the stem S. We want relative to
945 ourselves, so translate:
948 + s->relative_coordinate (commony, Y_AXIS)
949 - me->relative_coordinate (commony, Y_AXIS);
955 feasible_left_point.intersect (flp);
958 if (feasible_left_point.empty_b())
960 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
962 else if (!feasible_left_point.elem_b(y))
964 if (isinf (feasible_left_point[DOWN]))
965 y = feasible_left_point[UP] - REGION_SIZE;
966 else if (isinf (feasible_left_point[UP]))
967 y = feasible_left_point[DOWN]+ REGION_SIZE;
969 y = feasible_left_point.center ();
971 pos = Interval (y, (y+dy));
972 me->set_grob_property ("positions", ly_interval2scm (pos));
973 return SCM_UNSPECIFIED;
977 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
979 Beam::check_concave (SCM smob)
981 Grob *me = unsmob_grob (smob);
983 Link_array<Grob> stems =
984 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
986 for (int i = 0; i < stems.size ();)
988 if (Stem::invisible_b (stems[i]))
994 if (stems.size () < 3)
995 return SCM_UNSPECIFIED;
998 /* Concaveness #1: If distance of an inner notehead to line between
999 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1000 beam is concave (Heinz Stolba).
1002 In the case of knees, the line connecting outer heads is often
1003 not related to the beam slope (it may even go in the other
1004 direction). Skip the check when the outer stems point in
1005 different directions. --hwn
1008 bool concaveness1 = false;
1009 SCM gap = me->get_grob_property ("concaveness-gap");
1010 if (gh_number_p (gap)
1011 && Stem::get_direction(stems.top ())
1012 == Stem::get_direction(stems[0]))
1014 Real r1 = gh_scm2double (gap);
1015 Real dy = Stem::chord_start_y (stems.top ())
1016 - Stem::chord_start_y (stems[0]);
1019 Real slope = dy / (stems.size () - 1);
1021 Real y0 = Stem::chord_start_y (stems[0]);
1022 for (int i = 1; i < stems.size () - 1; i++)
1024 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1027 concaveness1 = true;
1034 /* Concaveness #2: Sum distances of inner noteheads that fall
1035 outside the interval of the two outer noteheads.
1037 We only do this for beams where first and last stem have the same
1041 Note that "convex" stems compensate for "concave" stems.
1042 (is that intentional?) --hwn.
1045 Real concaveness2 = 0;
1046 SCM thresh = me->get_grob_property ("concaveness-threshold");
1047 Real r2 = infinity_f;
1048 if (!concaveness1 && gh_number_p (thresh)
1049 && Stem::get_direction(stems.top ())
1050 == Stem::get_direction(stems[0]))
1052 r2 = gh_scm2double (thresh);
1054 Direction dir = Stem::get_direction(stems.top ());
1056 Interval iv (Stem::chord_start_y (stems[0]),
1057 Stem::chord_start_y (stems.top ()));
1059 if (iv[MAX] < iv[MIN])
1062 for (int i = 1; i < stems.size () - 1; i++)
1064 Real f = Stem::chord_start_y (stems[i]);
1065 concave += ((f - iv[MAX] ) >? 0) +
1066 ((f - iv[MIN] ) <? 0);
1069 concaveness2 = concave / (stems.size () - 2);
1071 /* ugh: this is the a kludge to get
1072 input/regression/beam-concave.ly to behave as
1076 huh? we're dividing twice (which is not scalable) meaning that
1077 the longer the beam, the more unlikely it will be
1078 concave. Maybe you would even expect the other way around??
1083 concaveness2 /= (stems.size () - 2);
1086 /* TODO: some sort of damping iso -> plain horizontal */
1087 if (concaveness1 || concaveness2 > r2)
1089 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1090 Real r = pos.linear_combination (0);
1091 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1092 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1095 return SCM_UNSPECIFIED;
1098 /* This neat trick is by Werner Lemberg,
1099 damped = tanh (slope)
1100 corresponds with some tables in [Wanske] CHECKME */
1101 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1103 Beam::slope_damping (SCM smob)
1105 Grob *me = unsmob_grob (smob);
1107 if (visible_stem_count (me) <= 1)
1108 return SCM_UNSPECIFIED;
1110 SCM s = me->get_grob_property ("damping");
1111 int damping = gh_scm2int (s);
1115 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1116 Real dy = pos.delta ();
1118 Grob *fvs = first_visible_stem (me);
1119 Grob *lvs = last_visible_stem (me);
1121 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1124 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1125 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1126 Real dydx = dy && dx ? dy/dx : 0;
1127 dydx = 0.6 * tanh (dydx) / damping;
1129 Real damped_dy = dydx * dx;
1130 pos[LEFT] += (dy - damped_dy) / 2;
1131 pos[RIGHT] -= (dy - damped_dy) / 2;
1133 me->set_grob_property ("positions", ly_interval2scm (pos));
1135 return SCM_UNSPECIFIED;
1139 Report slice containing the numbers that are both in (car BEAMING)
1143 where_are_the_whole_beams(SCM beaming)
1147 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1149 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1151 l.add_point (gh_scm2int (gh_car (s)));
1158 Calculate the Y position of the stem-end, given the Y-left, Y-right
1159 in POS for stem S. This Y position is relative to S.
1162 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1164 Interval pos, bool french)
1166 Real beam_translation = get_beam_translation (me);
1169 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1170 Real dy = pos.delta ();
1172 Real stem_y_beam0 = (dy && dx
1177 Direction my_dir = Directional_element_interface::get (s);
1178 SCM beaming = s->get_grob_property ("beaming");
1180 Real stem_y = stem_y_beam0;
1183 Slice bm = where_are_the_whole_beams (beaming);
1185 stem_y += beam_translation * bm[-my_dir];
1189 Slice bm = Stem::beam_multiplicity(s);
1191 stem_y +=bm[my_dir] * beam_translation;
1194 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1195 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1201 Hmm. At this time, beam position and slope are determined. Maybe,
1202 stem directions and length should set to relative to the chord's
1203 position of the beam. */
1205 Beam::set_stem_lengths (Grob *me)
1207 Link_array<Grob> stems=
1208 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1210 if (stems.size () <= 1)
1214 for (int a = 2; a--;)
1215 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1217 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1218 Real staff_space = Staff_symbol_referencer::staff_space (me);
1220 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1225 if (gh_number_p (me->get_grob_property ("gap"))
1226 &&gh_scm2double (me->get_grob_property ("gap")))
1229 thick = gh_scm2double (me->get_grob_property ("thickness"))
1230 * Staff_symbol_referencer::staff_space(me);
1233 // ugh -> use commonx
1234 Grob * fvs = first_visible_stem (me);
1235 Grob *lvs = last_visible_stem (me);
1237 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1238 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1240 for (int i=0; i < stems.size (); i++)
1243 if (Stem::invisible_b (s))
1246 Real stem_y = calc_stem_y (me, s, common,
1248 pos, french && i > 0&& (i < stems.size () -1));
1251 Make the stems go up to the end of the beam. This doesn't matter
1252 for normal beams, but for tremolo beams it looks silly otherwise.
1255 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1257 Stem::set_stemend (s, 2* stem_y / staff_space);
1262 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1264 Link_array<Grob> stems=
1265 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1268 for (int i=0; i < stems.size (); i++)
1271 Don't overwrite user settings.
1276 /* Don't set beaming for outside of outer stems */
1277 if ((d == LEFT && i == 0)
1278 ||(d == RIGHT && i == stems.size () -1))
1282 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1283 if (beaming_prop == SCM_EOL ||
1284 index_get_cell (beaming_prop, d) == SCM_EOL)
1286 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1287 Stem::set_beaming (stems[i], b, d);
1290 while (flip (&d) != LEFT);
1295 Beam::forced_stem_count (Grob *me)
1297 Link_array<Grob>stems =
1298 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1300 for (int i=0; i < stems.size (); i++)
1304 if (Stem::invisible_b (s))
1307 /* I can imagine counting those boundaries as a half forced stem,
1308 but let's count them full for now. */
1309 if (abs (Stem::chord_start_y (s)) > 0.1
1310 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1320 Beam::visible_stem_count (Grob *me)
1322 Link_array<Grob>stems =
1323 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1325 for (int i = stems.size (); i--;)
1327 if (!Stem::invisible_b (stems[i]))
1334 Beam::first_visible_stem (Grob *me)
1336 Link_array<Grob>stems =
1337 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1339 for (int i = 0; i < stems.size (); i++)
1341 if (!Stem::invisible_b (stems[i]))
1348 Beam::last_visible_stem (Grob *me)
1350 Link_array<Grob>stems =
1351 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1352 for (int i = stems.size (); i--;)
1354 if (!Stem::invisible_b (stems[i]))
1364 handle rest under beam (do_post: beams are calculated now)
1365 what about combination of collisions and rest under beam.
1369 rest -> stem -> beam -> interpolate_y_position ()
1371 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1373 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1375 Grob *rest = unsmob_grob (element_smob);
1376 Axis a = (Axis) gh_scm2int (axis);
1378 assert (a == Y_AXIS);
1380 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1383 return gh_double2scm (0.0);
1384 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1386 || !Beam::has_interface (beam)
1387 || !Beam::visible_stem_count (beam))
1388 return gh_double2scm (0.0);
1390 // make callback for rest from this.
1391 // todo: make sure this calced already.
1393 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1394 Interval pos (0, 0);
1395 SCM s = beam->get_grob_property ("positions");
1396 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1397 pos = ly_scm2interval (s);
1399 Real dy = pos.delta ();
1400 // ugh -> use commonx
1401 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1402 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1403 Real dydx = dy && dx ? dy/dx : 0;
1405 Direction d = Stem::get_direction (stem);
1406 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1408 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1411 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1414 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1416 minimum_dist + -d * (beamy - rest_dim) >? 0;
1418 int stafflines = Staff_symbol_referencer::line_count (rest);
1420 // move discretely by half spaces.
1421 int discrete_dist = int (ceil (dist));
1423 // move by whole spaces inside the staff.
1424 if (discrete_dist < stafflines+1)
1425 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1427 return gh_double2scm (-d * discrete_dist);
1431 Beam::knee_b (Grob* me)
1433 SCM k = me->get_grob_property ("knee");
1434 if (gh_boolean_p (k))
1435 return gh_scm2bool (k);
1439 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1441 Direction dir = Directional_element_interface::get
1442 (unsmob_grob (ly_car (s)));
1451 me->set_grob_property ("knee", gh_bool2scm (knee));
1457 Beam::get_direction_beam_count (Grob *me, Direction d )
1459 Link_array<Grob>stems =
1460 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1463 for (int i = stems.size (); i--;)
1466 Should we take invisible stems into account?
1468 if (Stem::get_direction (stems[i]) == d)
1469 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1476 ADD_INTERFACE (Beam, "beam-interface",
1479 #'thickness= weight of beams, in staffspace
1482 We take the least squares line through the ideal-length stems, and
1483 then damp that using
1485 damped = tanh (slope)
1487 this gives an unquantized left and right position for the beam end.
1488 Then we take all combinations of quantings near these left and right
1489 positions, and give them a score (according to how close they are to
1490 the ideal slope, how close the result is to the ideal stems, etc.). We
1491 take the best scoring combination.
1494 "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");