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>
14 * Use Number_pair i.s.o Interval to represent (yl, yr).
16 - Determine auto knees based on positions if it's set by the user.
22 - Stems run to the Y-center of the beam.
24 - beam_translation is the offset between Y centers of the beam.
29 #include <math.h> // tanh.
31 #include "molecule.hh"
32 #include "directional-element-interface.hh"
36 #include "least-squares.hh"
38 #include "paper-def.hh"
40 #include "group-interface.hh"
41 #include "staff-symbol-referencer.hh"
47 #define DEBUG_QUANTING 0
51 #include "text-item.hh" // debug output.
52 #include "font-interface.hh" // debug output.
57 Beam::add_stem (Grob *me, Grob *s)
59 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
61 s->add_dependency (me);
63 assert (!Stem::get_beam (s));
64 s->set_grob_property ("beam", me->self_scm ());
66 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
70 /* Return the translation between 2 adjoining beams. */
72 Beam::get_beam_translation (Grob *me)
74 SCM func = me->get_grob_property ("space-function");
75 SCM s = gh_call2 (func, me->self_scm (), scm_int2num (get_beam_count (me)));
76 return gh_scm2double (s);
79 /* Maximum beam_count. */
81 Beam::get_beam_count (Grob *me)
84 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
86 Grob *stem = unsmob_grob (ly_car (s));
87 m = m >? (Stem::beam_multiplicity (stem).length () + 1);
92 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
94 Beam::space_function (SCM smob, SCM beam_count)
96 Grob *me = unsmob_grob (smob);
98 Real staff_space = Staff_symbol_referencer::staff_space (me);
99 Real line = me->get_paper ()->get_var ("linethickness");
100 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
103 Real beam_translation = gh_scm2int (beam_count) < 4
104 ? (2*staff_space + line - thickness) / 2.0
105 : (3*staff_space + line - thickness) / 3.0;
107 return gh_double2scm (beam_translation);
111 /* After pre-processing all directions should be set.
112 Several post-processing routines (stem, slur, script) need stem/beam
114 Currenly, this means that beam has set all stem's directions.
115 [Alternatively, stems could set its own directions, according to
116 their beam, during 'final-pre-processing'.] */
117 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
119 Beam::before_line_breaking (SCM smob)
121 Grob *me = unsmob_grob (smob);
123 /* Beams with less than 2 two stems don't make much sense, but could happen
128 For a beam that only has one stem, we try to do some disappearance magic:
129 we revert the flag, and move on to The Eternal Engraving Fields. */
131 int count = visible_stem_count (me);
134 me->warning (_ ("beam has less than two visible stems"));
136 SCM stems = me->get_grob_property ("stems");
137 if (scm_ilength (stems) == 1)
139 me->warning (_ ("Beam has less than two stems. Removing beam."));
141 unsmob_grob (gh_car (stems))->set_grob_property ("beam", SCM_EOL);
144 return SCM_UNSPECIFIED;
146 else if (scm_ilength (stems) == 0)
149 return SCM_UNSPECIFIED;
154 Direction d = get_default_dir (me);
156 consider_auto_knees (me);
157 set_stem_directions (me, d);
161 set_stem_shorten (me);
169 We want a maximal number of shared beams, but if there is choice, we
170 take the one that is closest to the end of the stem. This is for situations like
183 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
187 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
191 for (int i = lslice[-left_dir];
192 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
195 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
197 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
198 if (scm_memq (scm_int2num (k), left_beaming) != SCM_BOOL_F)
202 if (count >= best_count)
213 Beam::connect_beams (Grob *me)
215 Link_array<Grob> stems=
216 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
219 last_int.set_empty();
220 SCM last_beaming = SCM_EOL;
221 Direction last_dir = CENTER;
222 for (int i = 0; i< stems.size(); i++)
224 Grob *this_stem = stems[i];
225 SCM this_beaming = this_stem->get_grob_property ("beaming");
227 Direction this_dir = Directional_element_interface::get(this_stem);
230 int start_point = position_with_maximal_common_beams
231 (last_beaming, this_beaming,
238 if (d == RIGHT && i == stems.size()-1)
241 new_slice.set_empty();
242 SCM s = index_get_cell (this_beaming, d);
243 for (; gh_pair_p (s); s = gh_cdr (s))
246 start_point - this_dir * gh_scm2int (gh_car (s));
248 new_slice.add_point (new_beam_pos);
249 gh_set_car_x (s, scm_int2num (new_beam_pos));
254 while (flip (&d) != LEFT);
256 if (!new_slice.empty_b())
257 last_int = new_slice;
261 gh_set_car_x ( this_beaming, SCM_EOL);
262 SCM s = gh_cdr (this_beaming);
263 for (; gh_pair_p (s); s = gh_cdr (s))
265 int np = - this_dir * gh_scm2int (gh_car(s));
266 gh_set_car_x (s, scm_int2num (np));
267 last_int.add_point (np);
271 if (i == stems.size () -1)
273 gh_set_cdr_x (this_beaming, SCM_EOL);
276 if (scm_ilength (gh_cdr (this_beaming)) > 0)
278 last_beaming = this_beaming;
284 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
286 Beam::brew_molecule (SCM grob)
288 Grob *me = unsmob_grob (grob);
289 Link_array<Grob> stems=
290 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
291 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
294 if (visible_stem_count (me))
296 // ugh -> use commonx
297 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
298 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
302 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
303 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
306 SCM posns = me->get_grob_property ("positions");
308 if (!ly_number_pair_p (posns))
310 programming_error ("No beam posns");
311 pos = Interval (0,0);
314 pos= ly_scm2interval (posns);
316 Real dy = pos.delta ();
317 Real dydx = dy && dx ? dy/dx : 0;
319 Real thick = gh_scm2double (me->get_grob_property ("thickness"))
320 * Staff_symbol_referencer::staff_space (me);
321 Real bdy = get_beam_translation (me);
323 SCM last_beaming = SCM_EOL;;
324 Real last_xposn = -1;
325 Real last_width = -1 ;
328 SCM gap = me->get_grob_property ("gap");
330 Real lt = me->get_paper ()->get_var ("linethickness");
331 for (int i = 0; i< stems.size(); i++)
335 SCM this_beaming = st->get_grob_property ("beaming");
336 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
337 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
341 SCM left = gh_cdr (last_beaming);
342 SCM right = gh_car (this_beaming);
344 Array<int> fullbeams;
345 Array<int> lfliebertjes;
346 Array<int> rfliebertjes;
349 gh_pair_p (s); s =gh_cdr (s))
351 int b = gh_scm2int (gh_car (s));
352 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
358 lfliebertjes.push (b);
362 gh_pair_p (s); s =gh_cdr (s))
364 int b = gh_scm2int (gh_car (s));
365 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
367 rfliebertjes.push (b);
372 Real w = xposn - last_xposn;
373 Real stem_offset = 0.0;
374 Real width_corr = 0.0;
377 stem_offset -= last_width/2;
378 width_corr += last_width/2;
381 if (i == stems.size() -1)
383 width_corr += stem_width/2;
386 if (gh_number_p (gap))
388 Real g = gh_scm2double (gap);
393 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
394 for (int j = fullbeams.size(); j--;)
397 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
398 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
399 the_beam.add_molecule (b);
402 if (lfliebertjes.size() || rfliebertjes.size())
406 if (!Stem::first_head (st))
410 int t = Stem::duration_log (st);
412 SCM proc = me->get_grob_property ("flag-width-function");
413 SCM result = gh_call1 (proc, scm_int2num (t));
414 nw_f = gh_scm2double (result);
417 /* Half beam should be one note-width,
418 but let's make sure two half-beams never touch */
420 Real w = xposn - last_xposn;
423 Molecule half = Lookup::beam (dydx, w, thick);
424 for (int j = lfliebertjes.size(); j--;)
427 b.translate_axis (last_xposn - x0, X_AXIS);
428 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
429 the_beam.add_molecule (b);
431 for (int j = rfliebertjes.size(); j--;)
434 b.translate_axis (xposn - x0 - w , X_AXIS);
435 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
436 the_beam.add_molecule (b);
442 last_width = stem_width;
443 last_beaming = this_beaming;
446 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
447 the_beam.translate_axis (pos[LEFT], Y_AXIS);
452 This code prints the demerits for each beam. Perhaps this
453 should be switchable for those who want to twiddle with the
459 str += to_string (gh_scm2int (me->get_grob_property ("best-idx")));
462 str += to_string (gh_scm2double (me->get_grob_property ("quant-score")),
465 SCM properties = Font_interface::font_alist_chain (me);
468 Molecule tm = Text_item::text2molecule (me, scm_makfrom0str (str.to_str0 ()), properties);
469 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
475 return the_beam.smobbed_copy();
482 Beam::get_default_dir (Grob *me)
484 Drul_array<int> total;
485 total[UP] = total[DOWN] = 0;
486 Drul_array<int> count;
487 count[UP] = count[DOWN] = 0;
490 Link_array<Grob> stems=
491 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
493 for (int i=0; i <stems.size (); i++)
496 Direction sd = Directional_element_interface::get (s);
498 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
499 int current = sd ? (1 + d * sd)/2 : center_distance;
506 } while (flip (&d) != DOWN);
508 SCM func = me->get_grob_property ("dir-function");
509 SCM s = gh_call2 (func,
510 gh_cons (scm_int2num (count[UP]),
511 scm_int2num (count[DOWN])),
512 gh_cons (scm_int2num (total[UP]),
513 scm_int2num (total[DOWN])));
515 if (gh_number_p (s) && gh_scm2int (s))
518 /* If dir is not determined: get default */
519 return to_dir (me->get_grob_property ("neutral-direction"));
523 /* Set all stems with non-forced direction to beam direction.
524 Urg: non-forced should become `without/with unforced' direction,
525 once stem gets cleaned-up. */
527 Beam::set_stem_directions (Grob *me, Direction d)
529 Link_array<Grob> stems
530 =Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
532 for (int i=0; i <stems.size (); i++)
536 SCM forcedir = s->get_grob_property ("direction");
537 if (!to_dir (forcedir))
538 Directional_element_interface::set (s, d);
543 A union of intervals in the real line.
545 Abysmal performance (quadratic) for large N, hopefully we don't have
546 that large N. In any case, this should probably be rewritten to use
551 Array<Interval> allowed_regions_;
560 allowed_regions_.clear();
563 allowed_regions_.push (s);
566 void remove_interval (Interval rm)
568 for (int i = 0; i < allowed_regions_.size(); )
572 s.intersect (allowed_regions_[i]);
576 Interval before = allowed_regions_[i];
577 Interval after = allowed_regions_[i];
579 before[RIGHT] = s[LEFT];
580 after[LEFT] = s[RIGHT];
582 if (!before.empty_b() && before.length () > 0.0)
584 allowed_regions_.insert (before, i);
587 allowed_regions_.del (i);
588 if (!after.empty_b () && after.length () > 0.0)
590 allowed_regions_.insert (after, i);
602 Only try horizontal beams for knees. No reliable detection of
603 anything else is possible here, since we don't know funky-beaming
604 settings, or X-distances (slopes!) People that want sloped
605 knee-beams, should set the directions manually.
608 Beam::consider_auto_knees (Grob* me)
610 SCM scm = me->get_grob_property ("auto-knee-gap");
611 if (!gh_number_p (scm))
614 Real threshold = gh_scm2double (scm);
620 Link_array<Grob> stems=
621 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
623 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
624 Real staff_space = Staff_symbol_referencer::staff_space (me);
626 Array<Interval> hps_array;
627 for (int i=0; i < stems.size (); i++)
629 Grob* stem = stems[i];
630 if (Stem::invisible_b (stem))
633 Interval hps = Stem::head_positions (stem);
638 hps *= staff_space * 0.5 ;
641 We could subtract beam Y position, but this routine only
642 sets stem directions, a constant shift does not have an
646 hps += stem->relative_coordinate (common, Y_AXIS);
648 if (to_dir (stem->get_grob_property ("direction")))
650 Direction stemdir = to_dir (stem->get_grob_property ("direction"));
651 hps[-stemdir] = - stemdir * infinity_f;
654 hps_array.push (hps);
656 gaps.remove_interval (hps);
660 Real max_gap_len =0.0;
662 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
664 Interval gap = gaps.allowed_regions_[i];
667 the outer gaps are not knees.
669 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
672 if (gap.length () >= max_gap_len)
674 max_gap_len = gap.length();
679 if (max_gap_len > threshold)
682 for (int i = 0; i < stems.size(); i++)
684 Grob* stem = stems[i];
685 if (Stem::invisible_b (stem))
688 Interval hps = hps_array[j++];
691 Direction d = (hps.center () < max_gap.center()) ?
694 stem->set_grob_property ("direction", scm_int2num (d));
696 hps.intersect (max_gap);
697 assert (hps.empty_b () || hps.length () < 1e-6 );
704 /* Set stem's shorten property if unset.
707 take some y-position (chord/beam/nearest?) into account
708 scmify forced-fraction
710 This is done in beam because the shorten has to be uniform over the
715 Beam::set_stem_shorten (Grob *me)
718 shortening looks silly for x staff beams
723 Real forced_fraction = 1.0 * forced_stem_count (me)
724 / visible_stem_count (me);
726 int beam_count = get_beam_count (me);
728 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
729 if (shorten_list == SCM_EOL)
732 Real staff_space = Staff_symbol_referencer::staff_space (me);
735 robust_list_ref (beam_count -1, shorten_list);
736 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
738 /* your similar cute comment here */
739 shorten_f *= forced_fraction;
742 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
745 /* Call list of y-dy-callbacks, that handle setting of
749 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
751 Beam::after_line_breaking (SCM smob)
753 Grob *me = unsmob_grob (smob);
755 /* Copy to mutable list. */
756 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
757 me->set_grob_property ("positions", s);
759 if (ly_car (s) == SCM_BOOL_F)
762 // one wonders if such genericity is necessary --hwn.
763 SCM callbacks = me->get_grob_property ("position-callbacks");
764 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
765 gh_call1 (ly_car (i), smob);
768 set_stem_lengths (me);
769 return SCM_UNSPECIFIED;
772 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
774 Beam::least_squares (SCM smob)
776 Grob *me = unsmob_grob (smob);
778 int count = visible_stem_count (me);
783 me->set_grob_property ("positions", ly_interval2scm (pos));
784 return SCM_UNSPECIFIED;
788 Array<Real> x_posns ;
789 Link_array<Grob> stems=
790 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
791 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
792 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
794 Real my_y = me->relative_coordinate (commony, Y_AXIS);
796 Grob *fvs = first_visible_stem (me);
797 Grob *lvs = last_visible_stem (me);
799 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
800 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
801 Stem::get_stem_info (lvs).ideal_y_
802 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
804 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
805 for (int i=0; i < stems.size (); i++)
809 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
812 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
820 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
821 Stem::chord_start_y (last_visible_stem (me)));
823 /* Simple beams (2 stems) on middle line should be allowed to be
826 However, if both stems reach middle line,
827 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
829 For that case, we apply artificial slope */
830 if (!ideal[LEFT] && chord.delta () && count == 2)
833 Direction d = (Direction) (sign (chord.delta ()) * UP);
834 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
848 Array<Offset> ideals;
849 for (int i=0; i < stems.size (); i++)
852 if (Stem::invisible_b (s))
854 ideals.push (Offset (x_posns[i],
855 Stem::get_stem_info (s).ideal_y_
856 + s->relative_coordinate (commony, Y_AXIS)
859 minimise_least_squares (&dydx, &y, ideals);
862 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
863 pos = Interval (y, (y+dy));
866 me->set_grob_property ("positions", ly_interval2scm (pos));
868 return SCM_UNSPECIFIED;
873 We can't combine with previous function, since check concave and
874 slope damping comes first.
876 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
878 Beam::shift_region_to_valid (SCM grob)
880 Grob *me = unsmob_grob (grob);
884 Array<Real> x_posns ;
885 Link_array<Grob> stems=
886 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
887 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
888 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
890 Grob *fvs = first_visible_stem (me);
893 return SCM_UNSPECIFIED;
895 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
896 for (int i=0; i < stems.size (); i++)
900 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
904 Grob *lvs = last_visible_stem (me);
906 return SCM_UNSPECIFIED;
908 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
910 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
911 Real dy = pos.delta();
917 Shift the positions so that we have a chance of finding good
918 quants (i.e. no short stem failures.)
920 Interval feasible_left_point;
921 feasible_left_point.set_full ();
922 for (int i=0; i < stems.size (); i++)
925 if (Stem::invisible_b (s))
928 Direction d = Stem::get_direction (s);
931 Stem::get_stem_info (s).shortest_y_
932 - dydx * x_posns [i];
935 left_y is now relative to the stem S. We want relative to
936 ourselves, so translate:
939 + s->relative_coordinate (commony, Y_AXIS)
940 - me->relative_coordinate (commony, Y_AXIS);
946 feasible_left_point.intersect (flp);
949 if (feasible_left_point.empty_b())
951 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
953 else if (!feasible_left_point.elem_b(y))
955 if (isinf (feasible_left_point[DOWN]))
956 y = feasible_left_point[UP] - REGION_SIZE;
957 else if (isinf (feasible_left_point[UP]))
958 y = feasible_left_point[DOWN]+ REGION_SIZE;
960 y = feasible_left_point.center ();
962 pos = Interval (y, (y+dy));
963 me->set_grob_property ("positions", ly_interval2scm (pos));
964 return SCM_UNSPECIFIED;
968 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
970 Beam::check_concave (SCM smob)
972 Grob *me = unsmob_grob (smob);
974 Link_array<Grob> stems =
975 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
977 for (int i = 0; i < stems.size ();)
979 if (Stem::invisible_b (stems[i]))
985 if (stems.size () < 3)
986 return SCM_UNSPECIFIED;
989 /* Concaveness #1: If distance of an inner notehead to line between
990 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
991 beam is concave (Heinz Stolba).
993 In the case of knees, the line connecting outer heads is often
994 not related to the beam slope (it may even go in the other
995 direction). Skip the check when the outer stems point in
996 different directions. --hwn
999 bool concaveness1 = false;
1000 SCM gap = me->get_grob_property ("concaveness-gap");
1001 if (gh_number_p (gap)
1002 && Stem::get_direction(stems.top ())
1003 == Stem::get_direction(stems[0]))
1005 Real r1 = gh_scm2double (gap);
1006 Real dy = Stem::chord_start_y (stems.top ())
1007 - Stem::chord_start_y (stems[0]);
1010 Real slope = dy / (stems.size () - 1);
1012 Real y0 = Stem::chord_start_y (stems[0]);
1013 for (int i = 1; i < stems.size () - 1; i++)
1015 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1018 concaveness1 = true;
1025 /* Concaveness #2: Sum distances of inner noteheads that fall
1026 outside the interval of the two outer noteheads.
1028 We only do this for beams where first and last stem have the same
1032 Note that "convex" stems compensate for "concave" stems.
1033 (is that intentional?) --hwn.
1036 Real concaveness2 = 0;
1037 SCM thresh = me->get_grob_property ("concaveness-threshold");
1038 Real r2 = infinity_f;
1039 if (!concaveness1 && gh_number_p (thresh)
1040 && Stem::get_direction(stems.top ())
1041 == Stem::get_direction(stems[0]))
1043 r2 = gh_scm2double (thresh);
1045 Direction dir = Stem::get_direction(stems.top ());
1047 Interval iv (Stem::chord_start_y (stems[0]),
1048 Stem::chord_start_y (stems.top ()));
1050 if (iv[MAX] < iv[MIN])
1053 for (int i = 1; i < stems.size () - 1; i++)
1055 Real f = Stem::chord_start_y (stems[i]);
1056 concave += ((f - iv[MAX] ) >? 0) +
1057 ((f - iv[MIN] ) <? 0);
1060 concaveness2 = concave / (stems.size () - 2);
1062 /* ugh: this is the a kludge to get
1063 input/regression/beam-concave.ly to behave as
1067 huh? we're dividing twice (which is not scalable) meaning that
1068 the longer the beam, the more unlikely it will be
1069 concave. Maybe you would even expect the other way around??
1074 concaveness2 /= (stems.size () - 2);
1077 /* TODO: some sort of damping iso -> plain horizontal */
1078 if (concaveness1 || concaveness2 > r2)
1080 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1081 Real r = pos.linear_combination (0);
1082 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1083 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1086 return SCM_UNSPECIFIED;
1089 /* This neat trick is by Werner Lemberg,
1090 damped = tanh (slope)
1091 corresponds with some tables in [Wanske] CHECKME */
1092 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1094 Beam::slope_damping (SCM smob)
1096 Grob *me = unsmob_grob (smob);
1098 if (visible_stem_count (me) <= 1)
1099 return SCM_UNSPECIFIED;
1101 SCM s = me->get_grob_property ("damping");
1102 int damping = gh_scm2int (s);
1106 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1107 Real dy = pos.delta ();
1109 Grob *fvs = first_visible_stem (me);
1110 Grob *lvs = last_visible_stem (me);
1112 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1115 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1116 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1117 Real dydx = dy && dx ? dy/dx : 0;
1118 dydx = 0.6 * tanh (dydx) / damping;
1120 Real damped_dy = dydx * dx;
1121 pos[LEFT] += (dy - damped_dy) / 2;
1122 pos[RIGHT] -= (dy - damped_dy) / 2;
1124 me->set_grob_property ("positions", ly_interval2scm (pos));
1126 return SCM_UNSPECIFIED;
1130 Report slice containing the numbers that are both in (car BEAMING)
1134 where_are_the_whole_beams(SCM beaming)
1138 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1140 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1142 l.add_point (gh_scm2int (gh_car (s)));
1148 /* Return the Y position of the stem-end, given the Y-left, Y-right
1149 in POS for stem S. This Y position is relative to S. */
1151 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1153 Interval pos, bool french)
1155 Real beam_translation = get_beam_translation (me);
1158 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1159 Real dy = pos.delta ();
1161 Real stem_y_beam0 = (dy && dx
1166 Direction my_dir = Directional_element_interface::get (s);
1167 SCM beaming = s->get_grob_property ("beaming");
1169 Real stem_y = stem_y_beam0;
1172 Slice bm = where_are_the_whole_beams (beaming);
1174 stem_y += beam_translation * bm[-my_dir];
1178 Slice bm = Stem::beam_multiplicity(s);
1180 stem_y +=bm[my_dir] * beam_translation;
1183 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1184 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1190 Hmm. At this time, beam position and slope are determined. Maybe,
1191 stem directions and length should set to relative to the chord's
1192 position of the beam. */
1194 Beam::set_stem_lengths (Grob *me)
1196 Link_array<Grob> stems=
1197 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1199 if (stems.size () <= 1)
1203 for (int a = 2; a--;)
1204 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1206 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1207 Real staff_space = Staff_symbol_referencer::staff_space (me);
1209 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1214 if (gh_number_p (me->get_grob_property ("gap"))
1215 &&gh_scm2double (me->get_grob_property ("gap")))
1218 thick = gh_scm2double (me->get_grob_property ("thickness"))
1219 * Staff_symbol_referencer::staff_space(me);
1222 // ugh -> use commonx
1223 Grob * fvs = first_visible_stem (me);
1224 Grob *lvs = last_visible_stem (me);
1226 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1227 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1229 for (int i=0; i < stems.size (); i++)
1232 if (Stem::invisible_b (s))
1235 Real stem_y = calc_stem_y (me, s, common,
1237 pos, french && i > 0&& (i < stems.size () -1));
1240 Make the stems go up to the end of the beam. This doesn't matter
1241 for normal beams, but for tremolo beams it looks silly otherwise.
1244 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1246 Stem::set_stemend (s, 2* stem_y / staff_space);
1251 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1253 Link_array<Grob> stems=
1254 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1257 for (int i=0; i < stems.size (); i++)
1260 Don't overwrite user settings.
1265 /* Don't set beaming for outside of outer stems */
1266 if ((d == LEFT && i == 0)
1267 ||(d == RIGHT && i == stems.size () -1))
1271 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1272 if (beaming_prop == SCM_EOL ||
1273 index_get_cell (beaming_prop, d) == SCM_EOL)
1275 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1276 Stem::set_beaming (stems[i], b, d);
1279 while (flip (&d) != LEFT);
1284 Beam::forced_stem_count (Grob *me)
1286 Link_array<Grob>stems =
1287 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1289 for (int i=0; i < stems.size (); i++)
1293 if (Stem::invisible_b (s))
1296 /* I can imagine counting those boundaries as a half forced stem,
1297 but let's count them full for now. */
1298 if (abs (Stem::chord_start_y (s)) > 0.1
1299 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1309 Beam::visible_stem_count (Grob *me)
1311 Link_array<Grob>stems =
1312 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1314 for (int i = stems.size (); i--;)
1316 if (!Stem::invisible_b (stems[i]))
1323 Beam::first_visible_stem (Grob *me)
1325 Link_array<Grob>stems =
1326 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1328 for (int i = 0; i < stems.size (); i++)
1330 if (!Stem::invisible_b (stems[i]))
1337 Beam::last_visible_stem (Grob *me)
1339 Link_array<Grob>stems =
1340 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1341 for (int i = stems.size (); i--;)
1343 if (!Stem::invisible_b (stems[i]))
1353 handle rest under beam (do_post: beams are calculated now)
1354 what about combination of collisions and rest under beam.
1358 rest -> stem -> beam -> interpolate_y_position ()
1360 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1362 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1364 Grob *rest = unsmob_grob (element_smob);
1365 Axis a = (Axis) gh_scm2int (axis);
1367 assert (a == Y_AXIS);
1369 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1372 return gh_double2scm (0.0);
1373 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1375 || !Beam::has_interface (beam)
1376 || !Beam::visible_stem_count (beam))
1377 return gh_double2scm (0.0);
1379 // make callback for rest from this.
1380 // todo: make sure this calced already.
1382 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1383 Interval pos (0, 0);
1384 SCM s = beam->get_grob_property ("positions");
1385 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1386 pos = ly_scm2interval (s);
1388 Real dy = pos.delta ();
1389 // ugh -> use commonx
1390 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1391 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1392 Real dydx = dy && dx ? dy/dx : 0;
1394 Direction d = Stem::get_direction (stem);
1395 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1397 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1400 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1403 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1405 minimum_dist + -d * (beamy - rest_dim) >? 0;
1407 int stafflines = Staff_symbol_referencer::line_count (rest);
1409 // move discretely by half spaces.
1410 int discrete_dist = int (ceil (dist));
1412 // move by whole spaces inside the staff.
1413 if (discrete_dist < stafflines+1)
1414 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1416 return gh_double2scm (-d * discrete_dist);
1420 Beam::knee_b (Grob* me)
1422 SCM k = me->get_grob_property ("knee");
1423 if (gh_boolean_p (k))
1424 return gh_scm2bool (k);
1428 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1430 Direction dir = Directional_element_interface::get
1431 (unsmob_grob (ly_car (s)));
1440 me->set_grob_property ("knee", gh_bool2scm (knee));
1446 Beam::get_direction_beam_count (Grob *me, Direction d )
1448 Link_array<Grob>stems =
1449 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1452 for (int i = stems.size (); i--;)
1455 Should we take invisible stems into account?
1457 if (Stem::get_direction (stems[i]) == d)
1458 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1465 ADD_INTERFACE (Beam, "beam-interface",
1468 #'thickness= weight of beams, in staffspace
1471 We take the least squares line through the ideal-length stems, and
1472 then damp that using
1474 damped = tanh (slope)
1476 this gives an unquantized left and right position for the beam end.
1477 Then we take all combinations of quantings near these left and right
1478 positions, and give them a score (according to how close they are to
1479 the ideal slope, how close the result is to the ideal stems, etc.). We
1480 take the best scoring combination.
1483 "knee 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");