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, d);
165 set_stem_directions (me, d);
169 set_stem_shorten (me);
177 We want a maximal number of shared beams, but if there is choice, we
178 take the one that is closest to the end of the stem. This is for situations like
191 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
195 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
199 for (int i = lslice[-left_dir];
200 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
203 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
205 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
206 if (scm_memq (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, Direction d)
630 SCM scm = me->get_grob_property ("auto-knee-gap");
631 if (!gh_number_p (scm))
634 Real threshold = gh_scm2double (scm);
641 Link_array<Grob> stems=
642 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
644 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
645 Real staff_space = Staff_symbol_referencer::staff_space (me);
647 Array<Interval> hps_array;
648 for (int i=0; i < stems.size (); i++)
650 Grob* stem = stems[i];
651 if (Stem::invisible_b (stem))
655 Interval hps = Stem::head_positions (stem);
661 hps *= staff_space * 0.5 ;
662 hps += stem->relative_coordinate (common, Y_AXIS);
664 if (to_boolean (stem->get_grob_property ("dir-forced")))
666 Direction stemdir =Directional_element_interface::get (stem);
667 hps[-stemdir] = - stemdir * infinity_f;
670 hps_array.push (hps);
672 gaps.remove_interval (hps);
676 Real max_gap_len =0.0;
678 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
680 Interval gap = gaps.allowed_regions_[i];
683 the outer gaps are not knees.
685 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
688 if (gap.length () >= max_gap_len)
690 max_gap_len = gap.length();
695 if (max_gap_len > threshold)
698 for (int i = 0; i < stems.size(); i++)
700 Grob* stem = stems[i];
701 if (Stem::invisible_b (stem))
704 Interval hps = hps_array[j++];
707 Direction d = (hps.center () < max_gap.center()) ?
710 stem->set_grob_property ("direction", scm_int2num (d));
713 UGH. Check why we still need dir-forced; I think we can
716 stem->set_grob_property ("dir-forced", SCM_BOOL_T);
718 hps.intersect (max_gap);
719 assert (hps.empty_b () || hps.length () < 1e-6 );
726 /* Set stem's shorten property if unset.
729 take some y-position (chord/beam/nearest?) into account
730 scmify forced-fraction
732 This is done in beam because the shorten has to be uniform over the
737 Beam::set_stem_shorten (Grob *me)
740 shortening looks silly for x staff beams
745 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
747 int beam_count = get_beam_count (me);
749 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
750 if (shorten == SCM_EOL)
753 int sz = scm_ilength (shorten);
755 Real staff_space = Staff_symbol_referencer::staff_space (me);
756 SCM shorten_elt = scm_list_ref (shorten,
757 scm_int2num (beam_count <? (sz - 1)));
758 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
760 /* your similar cute comment here */
761 shorten_f *= forced_fraction;
764 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
767 /* Call list of y-dy-callbacks, that handle setting of
771 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
773 Beam::after_line_breaking (SCM smob)
775 Grob *me = unsmob_grob (smob);
777 /* Copy to mutable list. */
778 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
779 me->set_grob_property ("positions", s);
781 if (ly_car (s) == SCM_BOOL_F)
784 // one wonders if such genericity is necessary --hwn.
785 SCM callbacks = me->get_grob_property ("position-callbacks");
786 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
787 gh_call1 (ly_car (i), smob);
790 set_stem_lengths (me);
791 return SCM_UNSPECIFIED;
794 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
796 Beam::least_squares (SCM smob)
798 Grob *me = unsmob_grob (smob);
800 int count = visible_stem_count (me);
805 me->set_grob_property ("positions", ly_interval2scm (pos));
806 return SCM_UNSPECIFIED;
810 Array<Real> x_posns ;
811 Link_array<Grob> stems=
812 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
813 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
814 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
816 Real my_y = me->relative_coordinate (commony, Y_AXIS);
818 Grob *fvs = first_visible_stem (me);
819 Grob *lvs = last_visible_stem (me);
821 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
822 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
823 Stem::calc_stem_info (lvs).ideal_y_
824 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
826 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
827 for (int i=0; i < stems.size (); i++)
831 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
834 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
842 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
843 Stem::chord_start_y (last_visible_stem (me)));
847 TODO -- use scoring for this.
849 complicated, because we take stem-info.ideal for determining
852 /* Make simple beam on middle line have small tilt */
853 if (!ideal[LEFT] && chord.delta () && count == 2)
859 Direction d = (Direction) (sign (chord.delta ()) * UP);
860 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
875 Array<Offset> ideals;
876 for (int i=0; i < stems.size (); i++)
879 if (Stem::invisible_b (s))
881 ideals.push (Offset (x_posns[i],
882 Stem::calc_stem_info (s).ideal_y_
883 + s->relative_coordinate (commony, Y_AXIS)
886 minimise_least_squares (&dydx, &y, ideals);
889 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
890 pos = Interval (y, (y+dy));
893 me->set_grob_property ("positions", ly_interval2scm (pos));
895 return SCM_UNSPECIFIED;
900 We can't combine with previous function, since check concave and
901 slope damping comes first.
903 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
905 Beam::shift_region_to_valid (SCM grob)
907 Grob *me = unsmob_grob (grob);
911 Array<Real> x_posns ;
912 Link_array<Grob> stems=
913 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
914 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
915 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
917 Grob *fvs = first_visible_stem (me);
920 return SCM_UNSPECIFIED;
922 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
923 for (int i=0; i < stems.size (); i++)
927 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
931 Grob *lvs = last_visible_stem (me);
933 return SCM_UNSPECIFIED;
935 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
937 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
938 Real dy = pos.delta();
944 Shift the positions so that we have a chance of finding good
945 quants (i.e. no short stem failures.)
947 Interval feasible_left_point;
948 feasible_left_point.set_full ();
949 for (int i=0; i < stems.size (); i++)
952 if (Stem::invisible_b (s))
955 Direction d = Stem::get_direction (s);
958 Stem::calc_stem_info (s).shortest_y_
959 - dydx * x_posns [i];
962 left_y is now relative to the stem S. We want relative to
963 ourselves, so translate:
966 + s->relative_coordinate (commony, Y_AXIS)
967 - me->relative_coordinate (commony, Y_AXIS);
973 feasible_left_point.intersect (flp);
976 if (feasible_left_point.empty_b())
978 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
980 else if (!feasible_left_point.elem_b(y))
982 if (isinf (feasible_left_point[DOWN]))
983 y = feasible_left_point[UP] - REGION_SIZE;
984 else if (isinf (feasible_left_point[UP]))
985 y = feasible_left_point[DOWN]+ REGION_SIZE;
987 y = feasible_left_point.center ();
989 pos = Interval (y, (y+dy));
990 me->set_grob_property ("positions", ly_interval2scm (pos));
991 return SCM_UNSPECIFIED;
995 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
997 Beam::check_concave (SCM smob)
999 Grob *me = unsmob_grob (smob);
1001 Link_array<Grob> stems =
1002 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1004 for (int i = 0; i < stems.size ();)
1006 if (Stem::invisible_b (stems[i]))
1012 if (stems.size () < 3)
1013 return SCM_UNSPECIFIED;
1016 /* Concaveness #1: If distance of an inner notehead to line between
1017 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1018 beam is concave (Heinz Stolba).
1020 In the case of knees, the line connecting outer heads is often
1021 not related to the beam slope (it may even go in the other
1022 direction). Skip the check when the outer stems point in
1023 different directions. --hwn
1026 bool concaveness1 = false;
1027 SCM gap = me->get_grob_property ("concaveness-gap");
1028 if (gh_number_p (gap)
1029 && Stem::get_direction(stems.top ())
1030 == Stem::get_direction(stems[0]))
1032 Real r1 = gh_scm2double (gap);
1033 Real dy = Stem::chord_start_y (stems.top ())
1034 - Stem::chord_start_y (stems[0]);
1037 Real slope = dy / (stems.size () - 1);
1039 Real y0 = Stem::chord_start_y (stems[0]);
1040 for (int i = 1; i < stems.size () - 1; i++)
1042 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1045 concaveness1 = true;
1052 /* Concaveness #2: Sum distances of inner noteheads that fall
1053 outside the interval of the two outer noteheads.
1055 We only do this for beams where first and last stem have the same
1059 Note that "convex" stems compensate for "concave" stems.
1060 (is that intentional?) --hwn.
1063 Real concaveness2 = 0;
1064 SCM thresh = me->get_grob_property ("concaveness-threshold");
1065 Real r2 = infinity_f;
1066 if (!concaveness1 && gh_number_p (thresh)
1067 && Stem::get_direction(stems.top ())
1068 == Stem::get_direction(stems[0]))
1070 r2 = gh_scm2double (thresh);
1072 Direction dir = Stem::get_direction(stems.top ());
1074 Interval iv (Stem::chord_start_y (stems[0]),
1075 Stem::chord_start_y (stems.top ()));
1077 if (iv[MAX] < iv[MIN])
1080 for (int i = 1; i < stems.size () - 1; i++)
1082 Real f = Stem::chord_start_y (stems[i]);
1083 concave += ((f - iv[MAX] ) >? 0) +
1084 ((f - iv[MIN] ) <? 0);
1087 concaveness2 = concave / (stems.size () - 2);
1089 /* ugh: this is the a kludge to get
1090 input/regression/beam-concave.ly to behave as
1094 huh? we're dividing twice (which is not scalable) meaning that
1095 the longer the beam, the more unlikely it will be
1096 concave. Maybe you would even expect the other way around??
1101 concaveness2 /= (stems.size () - 2);
1104 /* TODO: some sort of damping iso -> plain horizontal */
1105 if (concaveness1 || concaveness2 > r2)
1107 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1108 Real r = pos.linear_combination (0);
1109 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1110 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1113 return SCM_UNSPECIFIED;
1116 /* This neat trick is by Werner Lemberg,
1117 damped = tanh (slope)
1118 corresponds with some tables in [Wanske] CHECKME */
1119 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1121 Beam::slope_damping (SCM smob)
1123 Grob *me = unsmob_grob (smob);
1125 if (visible_stem_count (me) <= 1)
1126 return SCM_UNSPECIFIED;
1128 SCM s = me->get_grob_property ("damping");
1129 int damping = gh_scm2int (s);
1133 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1134 Real dy = pos.delta ();
1136 Grob *fvs = first_visible_stem (me);
1137 Grob *lvs = last_visible_stem (me);
1139 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1142 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1143 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1144 Real dydx = dy && dx ? dy/dx : 0;
1145 dydx = 0.6 * tanh (dydx) / damping;
1147 Real damped_dy = dydx * dx;
1148 pos[LEFT] += (dy - damped_dy) / 2;
1149 pos[RIGHT] -= (dy - damped_dy) / 2;
1151 me->set_grob_property ("positions", ly_interval2scm (pos));
1153 return SCM_UNSPECIFIED;
1157 Report slice containing the numbers that are both in (car BEAMING)
1161 where_are_the_whole_beams(SCM beaming)
1165 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1167 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1169 l.add_point (gh_scm2int (gh_car (s)));
1176 Calculate the Y position of the stem-end, given the Y-left, Y-right
1177 in POS for stem S. This Y position is relative to S.
1180 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1182 Interval pos, bool french)
1184 Real beam_translation = get_beam_translation (me);
1187 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1188 Real dy = pos.delta ();
1190 Real stem_y_beam0 = (dy && dx
1195 Direction my_dir = Directional_element_interface::get (s);
1196 SCM beaming = s->get_grob_property ("beaming");
1198 Real stem_y = stem_y_beam0;
1201 Slice bm = where_are_the_whole_beams (beaming);
1203 stem_y += beam_translation * bm[-my_dir];
1207 Slice bm = Stem::beam_multiplicity(s);
1209 stem_y +=bm[my_dir] * beam_translation;
1212 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1213 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1219 Hmm. At this time, beam position and slope are determined. Maybe,
1220 stem directions and length should set to relative to the chord's
1221 position of the beam. */
1223 Beam::set_stem_lengths (Grob *me)
1225 Link_array<Grob> stems=
1226 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1228 if (stems.size () <= 1)
1232 for (int a = 2; a--;)
1233 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1235 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1236 Real staff_space = Staff_symbol_referencer::staff_space (me);
1238 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1243 if (gh_number_p (me->get_grob_property ("gap"))
1244 &&gh_scm2double (me->get_grob_property ("gap")))
1247 thick = gh_scm2double (me->get_grob_property ("thickness"))
1248 * Staff_symbol_referencer::staff_space(me);
1251 // ugh -> use commonx
1252 Grob * fvs = first_visible_stem (me);
1253 Grob *lvs = last_visible_stem (me);
1255 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1256 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1258 for (int i=0; i < stems.size (); i++)
1261 if (Stem::invisible_b (s))
1264 Real stem_y = calc_stem_y (me, s, common,
1266 pos, french && i > 0&& (i < stems.size () -1));
1269 Make the stems go up to the end of the beam. This doesn't matter
1270 for normal beams, but for tremolo beams it looks silly otherwise.
1273 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1275 Stem::set_stemend (s, 2* stem_y / staff_space);
1280 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1282 Link_array<Grob> stems=
1283 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1286 for (int i=0; i < stems.size (); i++)
1289 Don't overwrite user settings.
1294 /* Don't set beaming for outside of outer stems */
1295 if ((d == LEFT && i == 0)
1296 ||(d == RIGHT && i == stems.size () -1))
1300 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1301 if (beaming_prop == SCM_EOL ||
1302 index_get_cell (beaming_prop, d) == SCM_EOL)
1304 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1305 Stem::set_beaming (stems[i], b, d);
1308 while (flip (&d) != LEFT);
1313 Beam::forced_stem_count (Grob *me)
1315 Link_array<Grob>stems =
1316 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1318 for (int i=0; i < stems.size (); i++)
1322 if (Stem::invisible_b (s))
1325 if (((int)Stem::chord_start_y (s))
1326 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1336 Beam::visible_stem_count (Grob *me)
1338 Link_array<Grob>stems =
1339 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1341 for (int i = stems.size (); i--;)
1343 if (!Stem::invisible_b (stems[i]))
1350 Beam::first_visible_stem (Grob *me)
1352 Link_array<Grob>stems =
1353 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1355 for (int i = 0; i < stems.size (); i++)
1357 if (!Stem::invisible_b (stems[i]))
1364 Beam::last_visible_stem (Grob *me)
1366 Link_array<Grob>stems =
1367 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1368 for (int i = stems.size (); i--;)
1370 if (!Stem::invisible_b (stems[i]))
1380 handle rest under beam (do_post: beams are calculated now)
1381 what about combination of collisions and rest under beam.
1385 rest -> stem -> beam -> interpolate_y_position ()
1387 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1389 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1391 Grob *rest = unsmob_grob (element_smob);
1392 Axis a = (Axis) gh_scm2int (axis);
1394 assert (a == Y_AXIS);
1396 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1399 return gh_double2scm (0.0);
1400 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1402 || !Beam::has_interface (beam)
1403 || !Beam::visible_stem_count (beam))
1404 return gh_double2scm (0.0);
1406 // make callback for rest from this.
1407 // todo: make sure this calced already.
1409 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1410 Interval pos (0, 0);
1411 SCM s = beam->get_grob_property ("positions");
1412 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1413 pos = ly_scm2interval (s);
1415 Real dy = pos.delta ();
1416 // ugh -> use commonx
1417 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1418 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1419 Real dydx = dy && dx ? dy/dx : 0;
1421 Direction d = Stem::get_direction (stem);
1422 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1424 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1427 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1430 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1432 minimum_dist + -d * (beamy - rest_dim) >? 0;
1434 int stafflines = Staff_symbol_referencer::line_count (rest);
1436 // move discretely by half spaces.
1437 int discrete_dist = int (ceil (dist));
1439 // move by whole spaces inside the staff.
1440 if (discrete_dist < stafflines+1)
1441 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1443 return gh_double2scm (-d * discrete_dist);
1447 Beam::knee_b (Grob*me)
1449 SCM k = me->get_grob_property ("knee");
1450 if (gh_boolean_p (k))
1451 return gh_scm2bool (k);
1455 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1457 Direction dir = Directional_element_interface::get
1458 (unsmob_grob (ly_car (s)));
1467 me->set_grob_property ("knee", gh_bool2scm (knee));
1472 ADD_INTERFACE (Beam, "beam-interface",
1475 #'thickness= weight of beams, in staffspace
1478 We take the least squares line through the ideal-length stems, and
1479 then damp that using
1481 damped = tanh (slope)
1483 this gives an unquantized left and right position for the beam end.
1484 Then we take all combinations of quantings near these left and right
1485 positions, and give them a score (according to how close they are to
1486 the ideal slope, how close the result is to the ideal stems, etc.). We
1487 take the best scoring combination.
1490 "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");