2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
17 * Use Number_pair i.s.o Interval to represent (yl, yr).
19 - Determine auto knees based on positions if it's set by the user.
25 - Stems run to the Y-center of the beam.
27 - beam_translation is the offset between Y centers of the beam.
32 #include <math.h> // tanh.
34 #include "molecule.hh"
35 #include "directional-element-interface.hh"
39 #include "least-squares.hh"
41 #include "paper-def.hh"
43 #include "group-interface.hh"
44 #include "staff-symbol-referencer.hh"
50 #define DEBUG_QUANTING 0
54 #include "text-item.hh" // debug output.
55 #include "font-interface.hh" // debug output.
60 Beam::add_stem (Grob *me, Grob *s)
62 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
64 s->add_dependency (me);
66 assert (!Stem::get_beam (s));
67 s->set_grob_property ("beam", me->self_scm ());
69 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
74 this returns the translation between 2 adjoining beams.
77 Beam::get_beam_translation (Grob *me)
79 SCM func = me->get_grob_property ("space-function");
80 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
81 return gh_scm2double (s);
88 Beam::get_beam_count (Grob *me)
91 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
93 Grob *sc = unsmob_grob (ly_car (s));
95 m = m >? (Stem::beam_multiplicity (sc).length () + 1);
100 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
102 Beam::space_function (SCM smob, SCM beam_count)
104 Grob *me = unsmob_grob (smob);
106 Real staff_space = Staff_symbol_referencer::staff_space (me);
107 Real line = me->get_paper ()->get_var ("linethickness");
108 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
111 Real beam_translation = gh_scm2int (beam_count) < 4
112 ? (2*staff_space + line - thickness) / 2.0
113 : (3*staff_space + line - thickness) / 3.0;
115 return gh_double2scm (beam_translation);
119 /* After pre-processing all directions should be set.
120 Several post-processing routines (stem, slur, script) need stem/beam
122 Currenly, this means that beam has set all stem's directions.
123 [Alternatively, stems could set its own directions, according to
124 their beam, during 'final-pre-processing'.] */
125 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
127 Beam::before_line_breaking (SCM smob)
129 Grob *me = unsmob_grob (smob);
131 /* Beams with less than 2 two stems don't make much sense, but could happen
136 For a beam that only has one stem, we try to do some disappearance magic:
137 we revert the flag, and move on to The Eternal Engraving Fields. */
139 int count = visible_stem_count (me);
142 me->warning (_ ("beam has less than two visible stems"));
144 SCM stems = me->get_grob_property ("stems");
145 if (scm_ilength (stems) == 1)
147 me->warning (_ ("Beam has less than two stems. Removing beam."));
149 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
152 return SCM_UNSPECIFIED;
154 else if (scm_ilength (stems) == 0)
157 return SCM_UNSPECIFIED;
162 Direction d = get_default_dir (me);
164 consider_auto_knees (me, d);
165 set_stem_directions (me, d);
169 set_stem_shorten (me);
178 Beam::connect_beams (Grob *me)
180 Link_array<Grob> stems=
181 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
184 last_int.set_empty();
185 for (int i = 0; i< stems.size(); i++)
187 Grob *this_stem = stems[i];
188 SCM this_beaming = this_stem->get_grob_property ("beaming");
190 Direction this_dir = Directional_element_interface::get(this_stem);
193 int start_point = last_int [this_dir];
199 if (d == RIGHT && i == stems.size()-1)
202 new_slice.set_empty();
203 SCM s = index_get_cell (this_beaming, d);
204 for (; gh_pair_p (s); s = gh_cdr (s))
207 start_point - this_dir * gh_scm2int (gh_car (s));
209 new_slice.add_point (new_beam_pos);
210 gh_set_car_x (s, gh_int2scm (new_beam_pos));
213 while (flip (&d) != LEFT);
215 if (!new_slice.empty_b())
216 last_int = new_slice;
220 gh_set_car_x ( this_beaming, SCM_EOL);
221 SCM s = gh_cdr (this_beaming);
222 for (; gh_pair_p (s); s = gh_cdr (s))
224 int np = - this_dir * gh_scm2int (gh_car(s));
225 gh_set_car_x (s, gh_int2scm (np));
226 last_int.add_point (np);
230 if (i == stems.size () -1)
232 gh_set_cdr_x ( this_beaming, SCM_EOL);
237 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
239 Beam::brew_molecule (SCM grob)
241 Grob *me = unsmob_grob (grob);
242 Link_array<Grob> stems=
243 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
244 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
247 if (visible_stem_count (me))
249 // ugh -> use commonx
250 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
251 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
255 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
256 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
259 SCM posns = me->get_grob_property ("positions");
261 if (!ly_number_pair_p (posns))
263 programming_error ("No beam posns");
264 pos = Interval (0,0);
267 pos= ly_scm2interval (posns);
269 Real dy = pos.delta ();
270 Real dydx = dy && dx ? dy/dx : 0;
272 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
273 Real bdy = get_beam_translation (me);
275 SCM last_beaming = SCM_EOL;;
276 Real last_xposn = -1;
277 Real last_width = -1 ;
280 SCM gap = me->get_grob_property ("gap");
282 Real lt = me->get_paper ()->get_var ("linethickness");
283 for (int i = 0; i< stems.size(); i++)
287 SCM this_beaming = st->get_grob_property ("beaming");
288 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
289 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
293 SCM left = gh_cdr (last_beaming);
294 SCM right = gh_car (this_beaming);
296 Array<int> fullbeams;
297 Array<int> lfliebertjes;
298 Array<int> rfliebertjes;
301 gh_pair_p (s); s =gh_cdr (s))
303 int b = gh_scm2int (gh_car (s));
304 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
310 lfliebertjes.push (b);
314 gh_pair_p (s); s =gh_cdr (s))
316 int b = gh_scm2int (gh_car (s));
317 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
319 rfliebertjes.push (b);
324 Real w = xposn - last_xposn;
325 Real stem_offset = 0.0;
326 Real width_corr = 0.0;
329 stem_offset -= last_width/2;
330 width_corr += last_width/2;
333 if (i == stems.size() -1)
335 width_corr += stem_width/2;
338 if (gh_number_p (gap))
340 Real g = gh_scm2double (gap);
345 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
346 for (int j = fullbeams.size(); j--;)
349 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
350 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
351 the_beam.add_molecule (b);
354 if (lfliebertjes.size() || rfliebertjes.size())
358 if (!Stem::first_head (st))
362 int t = Stem::duration_log (st);
364 SCM proc = me->get_grob_property ("flag-width-function");
365 SCM result = gh_call1 (proc, gh_int2scm (t));
366 nw_f = gh_scm2double (result);
369 /* Half beam should be one note-width,
370 but let's make sure two half-beams never touch */
372 Real w = xposn - last_xposn;
375 Molecule half = Lookup::beam (dydx, w, thick);
376 for (int j = lfliebertjes.size(); j--;)
379 b.translate_axis (last_xposn - x0, X_AXIS);
380 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
381 the_beam.add_molecule (b);
383 for (int j = rfliebertjes.size(); j--;)
386 b.translate_axis (xposn - x0 - w , X_AXIS);
387 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
388 the_beam.add_molecule (b);
394 last_width = stem_width;
395 last_beaming = this_beaming;
398 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
399 the_beam.translate_axis (pos[LEFT], Y_AXIS);
404 This code prints the demerits for each beam. Perhaps this
405 should be switchable for those who want to twiddle with the
411 str += to_string (gh_scm2int (me->get_grob_property ("best-idx")));
414 str += to_string (gh_scm2double (me->get_grob_property ("quant-score")),
417 SCM properties = Font_interface::font_alist_chain (me);
420 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.to_str0 ()), properties);
421 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
427 return the_beam.smobbed_copy();
434 Beam::get_default_dir (Grob *me)
436 Drul_array<int> total;
437 total[UP] = total[DOWN] = 0;
438 Drul_array<int> count;
439 count[UP] = count[DOWN] = 0;
442 Link_array<Item> stems=
443 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
445 for (int i=0; i <stems.size (); i++)
448 Direction sd = Directional_element_interface::get (s);
450 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
451 int current = sd ? (1 + d * sd)/2 : center_distance;
458 } while (flip (&d) != DOWN);
460 SCM func = me->get_grob_property ("dir-function");
461 SCM s = gh_call2 (func,
462 gh_cons (gh_int2scm (count[UP]),
463 gh_int2scm (count[DOWN])),
464 gh_cons (gh_int2scm (total[UP]),
465 gh_int2scm (total[DOWN])));
467 if (gh_number_p (s) && gh_scm2int (s))
470 /* If dir is not determined: get default */
471 return to_dir (me->get_grob_property ("neutral-direction"));
475 /* Set all stems with non-forced direction to beam direction.
476 Urg: non-forced should become `without/with unforced' direction,
477 once stem gets cleaned-up. */
479 Beam::set_stem_directions (Grob *me, Direction d)
481 Link_array<Item> stems
482 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
484 for (int i=0; i <stems.size (); i++)
487 /* For knees, non-forced stems should probably have their
488 natural direction. In any case, when knee, beam direction is
491 TODO: for x staff knees, set direction pointing to 'the other' staff, rather than natural.
494 Stem::get_direction (s); // this actually sets it, if necessary
497 SCM force = s->remove_grob_property ("dir-forced");
498 if (!gh_boolean_p (force) || !gh_scm2bool (force))
499 Directional_element_interface::set (s, d);
504 /* Simplistic auto-knees; only consider vertical gap between two
507 This may decide for a knee that's impossible to fit sane scoring
508 criteria (eg, stem lengths). We may need something smarter. */
510 Beam::consider_auto_knees (Grob *me, Direction d)
512 SCM scm = me->get_grob_property ("auto-knee-gap");
514 if (!gh_number_p (scm))
519 Real staff_space = Staff_symbol_referencer::staff_space (me);
520 Real gap = gh_scm2double (scm) / staff_space;
522 Link_array<Grob> stems=
523 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
525 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
528 for (int r=1; r < stems.size (); r++)
530 if (!Stem::invisible_b (stems[r-1]))
532 Grob *right = stems[r];
533 Grob *left = stems[l];
534 if (Stem::invisible_b (left))
536 if (Stem::invisible_b (right))
539 Real left_y = Stem::extremal_heads (left)[d]
540 ->relative_coordinate (common, Y_AXIS);
541 Real right_y = Stem::extremal_heads (right)[-d]
542 ->relative_coordinate (common, Y_AXIS);
544 Real dy = right_y - left_y;
549 Direction knee_dir = (right_y > left_y ? UP : DOWN);
550 if (!Stem::invisible_b (left)
551 && left->get_grob_property ("dir-forced") != SCM_BOOL_T)
553 Directional_element_interface::set (left, knee_dir);
554 left->set_grob_property ("dir-forced", SCM_BOOL_T);
557 if (!Stem::invisible_b (right)
558 && stems[r]->get_grob_property ("dir-forced") != SCM_BOOL_T)
560 Directional_element_interface::set (right, -knee_dir);
561 right->set_grob_property ("dir-forced", SCM_BOOL_T);
568 me->set_grob_property ("knee", SCM_BOOL_T);
570 for (int i=0; i < stems.size (); i++)
571 stems[i]->set_grob_property ("stem-info", SCM_EOL);
575 /* Set stem's shorten property if unset.
578 take some y-position (chord/beam/nearest?) into account
579 scmify forced-fraction
581 This is done in beam because the shorten has to be uniform over the
586 Beam::set_stem_shorten (Grob *me)
589 shortening looks silly for x staff beams
594 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
596 int beam_count = get_beam_count (me);
598 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
599 if (shorten == SCM_EOL)
602 int sz = scm_ilength (shorten);
604 Real staff_space = Staff_symbol_referencer::staff_space (me);
605 SCM shorten_elt = scm_list_ref (shorten,
606 gh_int2scm (beam_count <? (sz - 1)));
607 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
609 /* your similar cute comment here */
610 shorten_f *= forced_fraction;
613 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
616 /* Call list of y-dy-callbacks, that handle setting of
620 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
622 Beam::after_line_breaking (SCM smob)
624 Grob *me = unsmob_grob (smob);
626 /* Copy to mutable list. */
627 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
628 me->set_grob_property ("positions", s);
630 if (ly_car (s) == SCM_BOOL_F)
633 // one wonders if such genericity is necessary --hwn.
634 SCM callbacks = me->get_grob_property ("position-callbacks");
635 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
636 gh_call1 (ly_car (i), smob);
639 set_stem_lengths (me);
640 return SCM_UNSPECIFIED;
643 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
645 Beam::least_squares (SCM smob)
647 Grob *me = unsmob_grob (smob);
649 int count = visible_stem_count (me);
654 me->set_grob_property ("positions", ly_interval2scm (pos));
655 return SCM_UNSPECIFIED;
659 Array<Real> x_posns ;
660 Link_array<Grob> stems=
661 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
662 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
663 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
665 Real my_y = me->relative_coordinate (commony, Y_AXIS);
667 Grob *fvs = first_visible_stem (me);
668 Grob *lvs = last_visible_stem (me);
670 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
671 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
672 Stem::calc_stem_info (lvs).ideal_y_
673 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
675 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
676 for (int i=0; i < stems.size (); i++)
680 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
683 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
691 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
692 Stem::chord_start_y (last_visible_stem (me)));
696 TODO -- use scoring for this.
698 complicated, because we take stem-info.ideal for determining
701 /* Make simple beam on middle line have small tilt */
702 if (!ideal[LEFT] && chord.delta () && count == 2)
708 Direction d = (Direction) (sign (chord.delta ()) * UP);
709 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
724 Array<Offset> ideals;
725 for (int i=0; i < stems.size (); i++)
728 if (Stem::invisible_b (s))
730 ideals.push (Offset (x_posns[i],
731 Stem::calc_stem_info (s).ideal_y_
732 + s->relative_coordinate (commony, Y_AXIS)
735 minimise_least_squares (&dydx, &y, ideals);
738 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
739 pos = Interval (y, (y+dy));
742 me->set_grob_property ("positions", ly_interval2scm (pos));
744 return SCM_UNSPECIFIED;
749 We can't combine with previous function, since check concave and
750 slope damping comes first.
752 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
754 Beam::shift_region_to_valid (SCM grob)
756 Grob *me = unsmob_grob (grob);
760 Array<Real> x_posns ;
761 Link_array<Grob> stems=
762 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
763 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
764 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
766 Grob *fvs = first_visible_stem (me);
769 return SCM_UNSPECIFIED;
771 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
772 for (int i=0; i < stems.size (); i++)
776 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
780 Grob *lvs = last_visible_stem (me);
782 return SCM_UNSPECIFIED;
784 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
786 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
787 Real dy = pos.delta();
793 Shift the positions so that we have a chance of finding good
794 quants (i.e. no short stem failures.)
796 Interval feasible_left_point;
797 feasible_left_point.set_full ();
798 for (int i=0; i < stems.size (); i++)
801 if (Stem::invisible_b (s))
804 Direction d = Stem::get_direction (s);
807 Stem::calc_stem_info (s).shortest_y_
808 - dydx * x_posns [i];
811 left_y is now relative to the stem S. We want relative to
812 ourselves, so translate:
815 + s->relative_coordinate (commony, Y_AXIS)
816 - me->relative_coordinate (commony, Y_AXIS);
822 feasible_left_point.intersect (flp);
825 if (feasible_left_point.empty_b())
827 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
829 else if (!feasible_left_point.elem_b(y))
831 if (isinf (feasible_left_point[DOWN]))
832 y = feasible_left_point[UP] - REGION_SIZE;
833 else if (isinf (feasible_left_point[UP]))
834 y = feasible_left_point[DOWN]+ REGION_SIZE;
836 y = feasible_left_point.center ();
838 pos = Interval (y, (y+dy));
839 me->set_grob_property ("positions", ly_interval2scm (pos));
840 return SCM_UNSPECIFIED;
844 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
846 Beam::check_concave (SCM smob)
848 Grob *me = unsmob_grob (smob);
850 Link_array<Grob> stems =
851 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
853 for (int i = 0; i < stems.size ();)
855 if (Stem::invisible_b (stems[i]))
861 if (stems.size () < 3)
862 return SCM_UNSPECIFIED;
865 /* Concaveness #1: If distance of an inner notehead to line between
866 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
867 beam is concave (Heinz Stolba).
869 In the case of knees, the line connecting outer heads is often
870 not related to the beam slope (it may even go in the other
871 direction). Skip the check when the outer stems point in
872 different directions. --hwn
875 bool concaveness1 = false;
876 SCM gap = me->get_grob_property ("concaveness-gap");
877 if (gh_number_p (gap)
878 && Stem::get_direction(stems.top ())
879 == Stem::get_direction(stems[0]))
881 Real r1 = gh_scm2double (gap);
882 Real dy = Stem::chord_start_y (stems.top ())
883 - Stem::chord_start_y (stems[0]);
886 Real slope = dy / (stems.size () - 1);
888 Real y0 = Stem::chord_start_y (stems[0]);
889 for (int i = 1; i < stems.size () - 1; i++)
891 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
901 /* Concaveness #2: Sum distances of inner noteheads that fall
902 outside the interval of the two outer noteheads.
904 We only do this for beams where first and last stem have the same
908 Note that "convex" stems compensate for "concave" stems.
909 (is that intentional?) --hwn.
912 Real concaveness2 = 0;
913 SCM thresh = me->get_grob_property ("concaveness-threshold");
914 Real r2 = infinity_f;
915 if (!concaveness1 && gh_number_p (thresh)
916 && Stem::get_direction(stems.top ())
917 == Stem::get_direction(stems[0]))
919 r2 = gh_scm2double (thresh);
921 Direction dir = Stem::get_direction(stems.top ());
923 Interval iv (Stem::chord_start_y (stems[0]),
924 Stem::chord_start_y (stems.top ()));
926 if (iv[MAX] < iv[MIN])
929 for (int i = 1; i < stems.size () - 1; i++)
931 Real f = Stem::chord_start_y (stems[i]);
932 concave += ((f - iv[MAX] ) >? 0) +
933 ((f - iv[MIN] ) <? 0);
936 concaveness2 = concave / (stems.size () - 2);
938 /* ugh: this is the a kludge to get
939 input/regression/beam-concave.ly to behave as
943 huh? we're dividing twice (which is not scalable) meaning that
944 the longer the beam, the more unlikely it will be
945 concave. Maybe you would even expect the other way around??
950 concaveness2 /= (stems.size () - 2);
953 /* TODO: some sort of damping iso -> plain horizontal */
954 if (concaveness1 || concaveness2 > r2)
956 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
957 Real r = pos.linear_combination (0);
958 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
959 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
962 return SCM_UNSPECIFIED;
965 /* This neat trick is by Werner Lemberg,
966 damped = tanh (slope)
967 corresponds with some tables in [Wanske] CHECKME */
968 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
970 Beam::slope_damping (SCM smob)
972 Grob *me = unsmob_grob (smob);
974 if (visible_stem_count (me) <= 1)
975 return SCM_UNSPECIFIED;
977 SCM s = me->get_grob_property ("damping");
978 int damping = gh_scm2int (s);
982 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
983 Real dy = pos.delta ();
985 Grob *fvs = first_visible_stem (me);
986 Grob *lvs = last_visible_stem (me);
988 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
991 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
992 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
993 Real dydx = dy && dx ? dy/dx : 0;
994 dydx = 0.6 * tanh (dydx) / damping;
996 Real damped_dy = dydx * dx;
997 pos[LEFT] += (dy - damped_dy) / 2;
998 pos[RIGHT] -= (dy - damped_dy) / 2;
1000 me->set_grob_property ("positions", ly_interval2scm (pos));
1002 return SCM_UNSPECIFIED;
1006 where_are_the_whole_beams(SCM beaming)
1010 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1012 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1014 l.add_point (gh_scm2int (gh_car (s)));
1021 Calculate the Y position of the stem-end, given the Y-left, Y-right
1022 in POS for stem S. This Y position is relative to S.
1025 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1027 Interval pos, bool french)
1029 Real beam_translation = get_beam_translation (me);
1032 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1033 Real dy = pos.delta ();
1035 Real stem_y_beam0 = (dy && dx
1040 Direction my_dir = Directional_element_interface::get (s);
1041 SCM beaming = s->get_grob_property ("beaming");
1043 Real stem_y = stem_y_beam0;
1046 Slice bm = where_are_the_whole_beams (beaming);
1048 stem_y += beam_translation * bm[-my_dir];
1052 Slice bm = Stem::beam_multiplicity(s);
1054 stem_y +=bm[my_dir] * beam_translation;
1057 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1058 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1064 Hmm. At this time, beam position and slope are determined. Maybe,
1065 stem directions and length should set to relative to the chord's
1066 position of the beam. */
1068 Beam::set_stem_lengths (Grob *me)
1070 Link_array<Grob> stems=
1071 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1073 if (stems.size () <= 1)
1077 for (int a = 2; a--;)
1078 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1080 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1081 Real staff_space = Staff_symbol_referencer::staff_space (me);
1083 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1088 if (gh_number_p (me->get_grob_property ("gap"))
1089 &&gh_scm2double (me->get_grob_property ("gap")))
1092 thick = gh_scm2double (me->get_grob_property ("thickness"))
1093 * Staff_symbol_referencer::staff_space(me);
1096 // ugh -> use commonx
1097 Grob * fvs = first_visible_stem (me);
1098 Grob *lvs = last_visible_stem (me);
1100 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1101 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1103 for (int i=0; i < stems.size (); i++)
1106 if (Stem::invisible_b (s))
1109 Real stem_y = calc_stem_y (me, s, common,
1111 pos, french && i > 0&& (i < stems.size () -1));
1114 Make the stems go up to the end of the beam. This doesn't matter
1115 for normal beams, but for tremolo beams it looks silly otherwise.
1118 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1120 Stem::set_stemend (s, 2* stem_y / staff_space);
1125 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1127 Link_array<Grob> stems=
1128 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1131 for (int i=0; i < stems.size (); i++)
1134 Don't overwrite user settings.
1139 /* Don't set beaming for outside of outer stems */
1140 if ((d == LEFT && i == 0)
1141 ||(d == RIGHT && i == stems.size () -1))
1145 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1146 if (beaming_prop == SCM_EOL ||
1147 index_get_cell (beaming_prop, d) == SCM_EOL)
1149 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1150 Stem::set_beaming (stems[i], b, d);
1153 while (flip (&d) != LEFT);
1158 Beam::forced_stem_count (Grob *me)
1160 Link_array<Grob>stems =
1161 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1163 for (int i=0; i < stems.size (); i++)
1167 if (Stem::invisible_b (s))
1170 if (((int)Stem::chord_start_y (s))
1171 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1181 Beam::visible_stem_count (Grob *me)
1183 Link_array<Grob>stems =
1184 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1186 for (int i = stems.size (); i--;)
1188 if (!Stem::invisible_b (stems[i]))
1195 Beam::first_visible_stem (Grob *me)
1197 Link_array<Grob>stems =
1198 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1200 for (int i = 0; i < stems.size (); i++)
1202 if (!Stem::invisible_b (stems[i]))
1209 Beam::last_visible_stem (Grob *me)
1211 Link_array<Grob>stems =
1212 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1213 for (int i = stems.size (); i--;)
1215 if (!Stem::invisible_b (stems[i]))
1225 handle rest under beam (do_post: beams are calculated now)
1226 what about combination of collisions and rest under beam.
1230 rest -> stem -> beam -> interpolate_y_position ()
1232 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1234 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1236 Grob *rest = unsmob_grob (element_smob);
1237 Axis a = (Axis) gh_scm2int (axis);
1239 assert (a == Y_AXIS);
1241 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1244 return gh_double2scm (0.0);
1245 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1247 || !Beam::has_interface (beam)
1248 || !Beam::visible_stem_count (beam))
1249 return gh_double2scm (0.0);
1251 // make callback for rest from this.
1252 // todo: make sure this calced already.
1254 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1255 Interval pos (0, 0);
1256 SCM s = beam->get_grob_property ("positions");
1257 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1258 pos = ly_scm2interval (s);
1260 Real dy = pos.delta ();
1261 // ugh -> use commonx
1262 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1263 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1264 Real dydx = dy && dx ? dy/dx : 0;
1266 Direction d = Stem::get_direction (stem);
1267 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1269 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1272 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1275 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1277 minimum_dist + -d * (beamy - rest_dim) >? 0;
1279 int stafflines = Staff_symbol_referencer::line_count (rest);
1281 // move discretely by half spaces.
1282 int discrete_dist = int (ceil (dist));
1284 // move by whole spaces inside the staff.
1285 if (discrete_dist < stafflines+1)
1286 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1288 return gh_double2scm (-d * discrete_dist);
1292 Beam::knee_b (Grob*me)
1294 SCM k = me->get_grob_property ("knee");
1295 if (gh_boolean_p (k))
1296 return gh_scm2bool (k);
1300 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1301 if (d != Directional_element_interface::get (unsmob_grob (ly_car (s))))
1307 me->set_grob_property ("knee", gh_bool2scm (knee));
1313 ADD_INTERFACE (Beam, "beam-interface",
1316 #'thickness= weight of beams, in staffspace
1319 We take the least squares line through the ideal-length stems, and
1320 then damp that using
1322 damped = tanh (slope)
1324 this gives an unquantized left and right position for the beam end.
1325 Then we take all combinations of quantings near these left and right
1326 positions, and give them a score (according to how close they are to
1327 the ideal slope, how close the result is to the ideal stems, etc.). We
1328 take the best scoring combination.
1331 "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");