2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2003 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
13 * Use Number_pair i.s.o Interval to represent (yl, yr).
15 - 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"
48 bool debug_beam_quanting_flag;
52 #include "text-item.hh" // debug output.
53 #include "font-interface.hh" // debug output.
58 Beam::add_stem (Grob *me, Grob *s)
60 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
62 s->add_dependency (me);
64 assert (!Stem::get_beam (s));
65 s->set_grob_property ("beam", me->self_scm ());
67 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
72 Beam::get_thickness (Grob * me)
74 return robust_scm2double (me->get_grob_property ("thickness"), 0)
75 * Staff_symbol_referencer::staff_space (me);
78 /* Return the translation between 2 adjoining beams. */
80 Beam::get_beam_translation (Grob *me)
82 SCM func = me->get_grob_property ("space-function");
83 SCM s = gh_call2 (func, me->self_scm (), scm_int2num (get_beam_count (me)));
84 return gh_scm2double (s);
87 /* Maximum beam_count. */
89 Beam::get_beam_count (Grob *me)
92 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
94 Grob *stem = unsmob_grob (ly_car (s));
95 m = m >? (Stem::beam_multiplicity (stem).length () + 1);
102 Space return space between beams.
104 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
106 Beam::space_function (SCM smob, SCM beam_count)
108 Grob *me = unsmob_grob (smob);
110 Real staff_space = Staff_symbol_referencer::staff_space (me);
111 Real line = me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
112 Real thickness = get_thickness (me);
114 Real beam_translation = gh_scm2int (beam_count) < 4
115 ? (2*staff_space + line - thickness) / 2.0
116 : (3*staff_space + line - thickness) / 3.0;
118 return gh_double2scm (beam_translation);
122 /* After pre-processing all directions should be set.
123 Several post-processing routines (stem, slur, script) need stem/beam
125 Currenly, this means that beam has set all stem's directions.
126 [Alternatively, stems could set its own directions, according to
127 their beam, during 'final-pre-processing'.] */
128 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
130 Beam::before_line_breaking (SCM smob)
132 Grob *me = unsmob_grob (smob);
134 /* Beams with less than 2 two stems don't make much sense, but could happen
139 For a beam that only has one stem, we try to do some disappearance magic:
140 we revert the flag, and move on to The Eternal Engraving Fields. */
142 int count = visible_stem_count (me);
145 me->warning (_ ("beam has less than two visible stems"));
147 SCM stems = me->get_grob_property ("stems");
148 if (scm_ilength (stems) == 1)
150 me->warning (_ ("Beam has less than two stems. Removing beam."));
152 unsmob_grob (gh_car (stems))->set_grob_property ("beam", SCM_EOL);
155 return SCM_UNSPECIFIED;
157 else if (scm_ilength (stems) == 0)
160 return SCM_UNSPECIFIED;
165 Direction d = get_default_dir (me);
167 consider_auto_knees (me);
168 set_stem_directions (me, d);
172 set_stem_shorten (me);
180 We want a maximal number of shared beams, but if there is choice, we
181 take the one that is closest to the end of the stem. This is for situations like
194 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
198 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
202 for (int i = lslice[-left_dir];
203 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
206 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
208 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
209 if (scm_memq (scm_int2num (k), left_beaming) != SCM_BOOL_F)
213 if (count >= best_count)
224 Beam::connect_beams (Grob *me)
226 Link_array<Grob> stems=
227 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
230 last_int.set_empty();
231 SCM last_beaming = SCM_EOL;
232 Direction last_dir = CENTER;
233 for (int i = 0; i< stems.size(); i++)
235 Grob *this_stem = stems[i];
236 SCM this_beaming = this_stem->get_grob_property ("beaming");
238 Direction this_dir = get_grob_direction (this_stem);
239 if (gh_pair_p (last_beaming) && gh_pair_p (this_beaming))
241 int start_point = position_with_maximal_common_beams
242 (last_beaming, this_beaming,
249 if (d == RIGHT && i == stems.size()-1)
252 new_slice.set_empty();
253 SCM s = index_get_cell (this_beaming, d);
254 for (; gh_pair_p (s); s = gh_cdr (s))
257 start_point - this_dir * gh_scm2int (gh_car (s));
259 new_slice.add_point (new_beam_pos);
260 gh_set_car_x (s, scm_int2num (new_beam_pos));
265 while (flip (&d) != LEFT);
267 if (!new_slice.is_empty ())
268 last_int = new_slice;
272 gh_set_car_x ( this_beaming, SCM_EOL);
273 SCM s = gh_cdr (this_beaming);
274 for (; gh_pair_p (s); s = gh_cdr (s))
276 int np = - this_dir * gh_scm2int (gh_car(s));
277 gh_set_car_x (s, scm_int2num (np));
278 last_int.add_point (np);
282 if (i == stems.size () -1)
284 gh_set_cdr_x (this_beaming, SCM_EOL);
287 if (scm_ilength (gh_cdr (this_beaming)) > 0)
289 last_beaming = this_beaming;
295 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
297 Beam::brew_molecule (SCM grob)
299 Grob *me = unsmob_grob (grob);
302 Link_array<Grob> stems=
303 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
304 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
307 if (visible_stem_count (me))
309 // ugh -> use commonx
310 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
311 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
315 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
316 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
319 SCM posns = me->get_grob_property ("positions");
321 if (!is_number_pair (posns))
323 programming_error ("No beam posns");
324 pos = Interval (0,0);
327 pos= ly_scm2interval (posns);
329 Real dy = pos.delta ();
330 Real dydx = (dy && dx) ? dy/dx : 0;
332 Real thick = get_thickness (me);
333 Real bdy = get_beam_translation (me);
335 SCM last_beaming = SCM_EOL;;
336 Real last_xposn = -1;
337 Real last_stem_width = -1 ;
339 Real gap_length =robust_scm2double ( me->get_grob_property ("gap"), 0.0);
342 Real lt = me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
344 for (int i = 0; i<= stems.size(); i++)
346 Grob * st = (i < stems.size()) ? stems[i] : 0;
348 SCM this_beaming = st ? st->get_grob_property ("beaming") : SCM_EOL;
349 Real xposn = st ? st->relative_coordinate (xcommon, X_AXIS) : 0.0;
350 Real stem_width = st ? robust_scm2double (st->get_grob_property ("thickness"), 1.0) *lt : 0 ;
351 Direction stem_dir = st ? to_dir (st->get_grob_property ("direction")) : CENTER;
353 We do the space left of ST, with lfliebertjes pointing to the
354 right from the left stem, and rfliebertjes pointing left from
357 SCM left = (i>0) ? gh_cdr (last_beaming) : SCM_EOL;
358 SCM right = st ? gh_car (this_beaming) : SCM_EOL;
360 Array<int> full_beams;
361 Array<int> lfliebertjes;
362 Array<int> rfliebertjes;
365 gh_pair_p (s); s =gh_cdr (s))
367 int b = gh_scm2int (gh_car (s));
368 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
374 lfliebertjes.push (b);
378 gh_pair_p (s); s =gh_cdr (s))
380 int b = gh_scm2int (gh_car (s));
381 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
383 rfliebertjes.push (b);
388 how much to stick out for beams across linebreaks
390 Real break_overshoot = 3.0;
391 Real w = (i > 0 && st) ? xposn - last_xposn : break_overshoot;
393 Real stem_offset =0.0;
396 w += last_stem_width / 2;
397 stem_offset = -last_stem_width / 2;
404 Real blot = me->get_paper ()->get_realvar (ly_symbol2scm ("blotdiameter"));
405 Molecule whole = Lookup::beam (dydx, w, thick, blot);
409 if (gh_number_p (me->get_grob_property ("gap-count")))
411 gap_count = gh_scm2int (me->get_grob_property ("gap-count"));
412 gapped = Lookup::beam (dydx, w - 2 * gap_length, thick, blot);
414 full_beams.sort (default_compare);
416 full_beams.reverse ();
420 for (int j = full_beams.size (); j--;)
427 b.translate_axis (gap_length, X_AXIS);
429 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
430 b.translate_axis (dydx * (last_xposn - x0) + bdy * full_beams[j], Y_AXIS);
432 the_beam.add_molecule (b);
437 if (lfliebertjes.size() || rfliebertjes.size())
443 int t = Stem::duration_log (st);
445 SCM proc = me->get_grob_property ("flag-width-function");
446 SCM result = gh_call1 (proc, scm_int2num (t));
447 nw_f = gh_scm2double (result);
450 nw_f = break_overshoot;
452 /* Half beam should be one note-width,
453 but let's make sure two half-beams never touch */
454 Real w = (i>0 && st) ? (xposn - last_xposn) : break_overshoot;
457 Molecule half = Lookup::beam (dydx, w, thick, blot);
458 for (int j = lfliebertjes.size(); j--;)
461 b.translate_axis (last_xposn - x0, X_AXIS);
462 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
463 the_beam.add_molecule (b);
465 for (int j = rfliebertjes.size(); j--;)
468 b.translate_axis (xposn - x0 - w , X_AXIS);
469 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
470 the_beam.add_molecule (b);
476 last_stem_width = stem_width;
477 last_beaming = this_beaming;
480 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
481 the_beam.translate_axis (pos[LEFT], Y_AXIS);
484 SCM quant_score = me->get_grob_property ("quant-score");
485 if (debug_beam_quanting_flag
486 && gh_string_p (quant_score))
490 This code prints the demerits for each beam. Perhaps this
491 should be switchable for those who want to twiddle with the
495 SCM properties = Font_interface::font_alist_chain (me);
497 Molecule tm = *unsmob_molecule (Text_item::interpret_markup
498 (me->get_paper ()->self_scm (), properties, quant_score));
499 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0, 0);
505 return the_beam.smobbed_copy();
512 Beam::get_default_dir (Grob *me)
514 Drul_array<int> total;
515 total[UP] = total[DOWN] = 0;
516 Drul_array<int> count;
517 count[UP] = count[DOWN] = 0;
520 Link_array<Grob> stems=
521 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
523 for (int i=0; i <stems.size (); i++)
526 Direction sd = get_grob_direction (s);
528 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
529 int current = sd ? (1 + d * sd)/2 : center_distance;
536 } while (flip (&d) != DOWN);
538 SCM func = me->get_grob_property ("dir-function");
539 SCM s = gh_call2 (func,
540 gh_cons (scm_int2num (count[UP]),
541 scm_int2num (count[DOWN])),
542 gh_cons (scm_int2num (total[UP]),
543 scm_int2num (total[DOWN])));
545 if (gh_number_p (s) && gh_scm2int (s))
548 /* If dir is not determined: get default */
549 return to_dir (me->get_grob_property ("neutral-direction"));
553 /* Set all stems with non-forced direction to beam direction.
554 Urg: non-forced should become `without/with unforced' direction,
555 once stem gets cleaned-up. */
557 Beam::set_stem_directions (Grob *me, Direction d)
559 Link_array<Grob> stems
560 =Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
562 for (int i=0; i <stems.size (); i++)
566 SCM forcedir = s->get_grob_property ("direction");
567 if (!to_dir (forcedir))
568 set_grob_direction (s, d);
573 A union of intervals in the real line.
575 Abysmal performance (quadratic) for large N, hopefully we don't have
576 that large N. In any case, this should probably be rewritten to use
581 Array<Interval> allowed_regions_;
590 allowed_regions_.clear();
593 allowed_regions_.push (s);
596 void remove_interval (Interval rm)
598 for (int i = 0; i < allowed_regions_.size(); )
602 s.intersect (allowed_regions_[i]);
606 Interval before = allowed_regions_[i];
607 Interval after = allowed_regions_[i];
609 before[RIGHT] = s[LEFT];
610 after[LEFT] = s[RIGHT];
612 if (!before.is_empty () && before.length () > 0.0)
614 allowed_regions_.insert (before, i);
617 allowed_regions_.del (i);
618 if (!after.is_empty () && after.length () > 0.0)
620 allowed_regions_.insert (after, i);
632 Only try horizontal beams for knees. No reliable detection of
633 anything else is possible here, since we don't know funky-beaming
634 settings, or X-distances (slopes!) People that want sloped
635 knee-beams, should set the directions manually.
638 Beam::consider_auto_knees (Grob* me)
640 SCM scm = me->get_grob_property ("auto-knee-gap");
641 if (!gh_number_p (scm))
644 Real threshold = gh_scm2double (scm);
650 Link_array<Grob> stems=
651 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
653 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
654 Real staff_space = Staff_symbol_referencer::staff_space (me);
656 Array<Interval> hps_array;
657 for (int i=0; i < stems.size (); i++)
659 Grob* stem = stems[i];
660 if (Stem::invisible_b (stem))
663 Interval hps = Stem::head_positions (stem);
668 hps *= staff_space * 0.5 ;
671 We could subtract beam Y position, but this routine only
672 sets stem directions, a constant shift does not have an
676 hps += stem->relative_coordinate (common, Y_AXIS);
678 if (to_dir (stem->get_grob_property ("direction")))
680 Direction stemdir = to_dir (stem->get_grob_property ("direction"));
681 hps[-stemdir] = - stemdir * infinity_f;
684 hps_array.push (hps);
686 gaps.remove_interval (hps);
690 Real max_gap_len =0.0;
692 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
694 Interval gap = gaps.allowed_regions_[i];
697 the outer gaps are not knees.
699 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
702 if (gap.length () >= max_gap_len)
704 max_gap_len = gap.length();
709 if (max_gap_len > threshold)
712 for (int i = 0; i < stems.size(); i++)
714 Grob* stem = stems[i];
715 if (Stem::invisible_b (stem))
718 Interval hps = hps_array[j++];
721 Direction d = (hps.center () < max_gap.center()) ?
724 stem->set_grob_property ("direction", scm_int2num (d));
726 hps.intersect (max_gap);
727 assert (hps.is_empty () || hps.length () < 1e-6 );
734 /* Set stem's shorten property if unset.
737 take some y-position (chord/beam/nearest?) into account
738 scmify forced-fraction
740 This is done in beam because the shorten has to be uniform over the
745 Beam::set_stem_shorten (Grob *me)
748 shortening looks silly for x staff beams
753 Real forced_fraction = 1.0 * forced_stem_count (me)
754 / visible_stem_count (me);
756 int beam_count = get_beam_count (me);
758 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
759 if (shorten_list == SCM_EOL)
762 Real staff_space = Staff_symbol_referencer::staff_space (me);
765 robust_list_ref (beam_count -1, shorten_list);
766 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
768 /* your similar cute comment here */
769 shorten_f *= forced_fraction;
772 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
775 /* Call list of y-dy-callbacks, that handle setting of
779 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
781 Beam::after_line_breaking (SCM smob)
783 Grob *me = unsmob_grob (smob);
786 return SCM_UNSPECIFIED;
790 Beam::position_beam (Grob *me)
792 if (to_boolean (me->get_grob_property ("positioning-done")))
795 me->set_grob_property ("positioning-done", SCM_BOOL_T);
797 /* Copy to mutable list. */
798 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
799 me->set_grob_property ("positions", s);
801 if (ly_car (s) == SCM_BOOL_F)
803 // one wonders if such genericity is necessary --hwn.
804 SCM callbacks = me->get_grob_property ("position-callbacks");
805 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
806 gh_call1 (ly_car (i), me->self_scm ());
809 set_stem_lengths (me);
814 Compute a first approximation to the beam slope.
816 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
818 Beam::least_squares (SCM smob)
820 Grob *me = unsmob_grob (smob);
822 int count = visible_stem_count (me);
827 me->set_grob_property ("positions", ly_interval2scm (pos));
828 return SCM_UNSPECIFIED;
832 Array<Real> x_posns ;
833 Link_array<Grob> stems=
834 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
835 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
836 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
838 Real my_y = me->relative_coordinate (commony, Y_AXIS);
840 Grob *fvs = first_visible_stem (me);
841 Grob *lvs = last_visible_stem (me);
843 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
844 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
845 Stem::get_stem_info (lvs).ideal_y_
846 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
848 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
849 for (int i=0; i < stems.size (); i++)
853 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
856 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
865 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
866 Stem::chord_start_y (last_visible_stem (me)));
868 /* Simple beams (2 stems) on middle line should be allowed to be
871 However, if both stems reach middle line,
872 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
874 For that case, we apply artificial slope */
875 if (!ideal[LEFT] && chord.delta () && count == 2)
878 Direction d = (Direction) (sign (chord.delta ()) * UP);
879 pos[d] = get_thickness (me) / 2;
888 For broken beams this doesn't work well. In this case, the
889 slope esp. of the first part of a broken beam should predict
890 where the second part goes.
902 Array<Offset> ideals;
903 for (int i=0; i < stems.size (); i++)
906 if (Stem::invisible_b (s))
908 ideals.push (Offset (x_posns[i],
909 Stem::get_stem_info (s).ideal_y_
910 + s->relative_coordinate (commony, Y_AXIS)
914 minimise_least_squares (&dydx, &y, ideals);
917 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
918 pos = Interval (y, (y+dy));
921 me->set_grob_property ("positions", ly_interval2scm (pos));
923 return SCM_UNSPECIFIED;
928 We can't combine with previous function, since check concave and
929 slope damping comes first.
931 TODO: we should use the concaveness to control the amount of damping
935 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
937 Beam::shift_region_to_valid (SCM grob)
939 Grob *me = unsmob_grob (grob);
943 Array<Real> x_posns ;
944 Link_array<Grob> stems=
945 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
946 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
947 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
949 Grob *fvs = first_visible_stem (me);
952 return SCM_UNSPECIFIED;
954 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
955 for (int i=0; i < stems.size (); i++)
959 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
963 Grob *lvs = last_visible_stem (me);
965 return SCM_UNSPECIFIED;
967 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
969 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
970 Real dy = pos.delta();
976 Shift the positions so that we have a chance of finding good
977 quants (i.e. no short stem failures.)
979 Interval feasible_left_point;
980 feasible_left_point.set_full ();
981 for (int i=0; i < stems.size (); i++)
984 if (Stem::invisible_b (s))
987 Direction d = Stem::get_direction (s);
990 Stem::get_stem_info (s).shortest_y_
991 - dydx * x_posns [i];
994 left_y is now relative to the stem S. We want relative to
995 ourselves, so translate:
998 + s->relative_coordinate (commony, Y_AXIS)
999 - me->relative_coordinate (commony, Y_AXIS);
1005 feasible_left_point.intersect (flp);
1008 if (feasible_left_point.is_empty ())
1010 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1012 else if (!feasible_left_point.contains (y))
1014 if (isinf (feasible_left_point[DOWN]))
1015 y = feasible_left_point[UP] - REGION_SIZE;
1016 else if (isinf (feasible_left_point[UP]))
1017 y = feasible_left_point[DOWN]+ REGION_SIZE;
1019 y = feasible_left_point.center ();
1021 pos = Interval (y, (y+dy));
1022 me->set_grob_property ("positions", ly_interval2scm (pos));
1023 return SCM_UNSPECIFIED;
1027 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1029 Beam::check_concave (SCM smob)
1031 Grob *me = unsmob_grob (smob);
1033 Link_array<Grob> stems =
1034 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1036 for (int i = 0; i < stems.size ();)
1038 if (Stem::invisible_b (stems[i]))
1044 if (stems.size () < 3)
1045 return SCM_UNSPECIFIED;
1048 /* Concaveness #1: If distance of an inner notehead to line between
1049 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1050 beam is concave (Heinz Stolba).
1052 In the case of knees, the line connecting outer heads is often
1053 not related to the beam slope (it may even go in the other
1054 direction). Skip the check when the outer stems point in
1055 different directions. --hwn
1058 bool concaveness1 = false;
1059 SCM gap = me->get_grob_property ("concaveness-gap");
1060 if (gh_number_p (gap)
1061 && Stem::get_direction(stems.top ())
1062 == Stem::get_direction(stems[0]))
1064 Real r1 = gh_scm2double (gap);
1065 Real dy = Stem::chord_start_y (stems.top ())
1066 - Stem::chord_start_y (stems[0]);
1069 Real slope = dy / (stems.size () - 1);
1071 Real y0 = Stem::chord_start_y (stems[0]);
1072 for (int i = 1; i < stems.size () - 1; i++)
1074 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1077 concaveness1 = true;
1084 /* Concaveness #2: Sum distances of inner noteheads that fall
1085 outside the interval of the two outer noteheads.
1087 We only do this for beams where first and last stem have the same
1091 Note that "convex" stems compensate for "concave" stems.
1092 (is that intentional?) --hwn.
1095 Real concaveness2 = 0;
1096 SCM thresh = me->get_grob_property ("concaveness-threshold");
1097 Real r2 = infinity_f;
1098 if (!concaveness1 && gh_number_p (thresh)
1099 && Stem::get_direction(stems.top ())
1100 == Stem::get_direction(stems[0]))
1102 r2 = gh_scm2double (thresh);
1104 Direction dir = Stem::get_direction(stems.top ());
1106 Interval iv (Stem::chord_start_y (stems[0]),
1107 Stem::chord_start_y (stems.top ()));
1109 if (iv[MAX] < iv[MIN])
1112 for (int i = 1; i < stems.size () - 1; i++)
1114 Real f = Stem::chord_start_y (stems[i]);
1115 concave += ((f - iv[MAX] ) >? 0) +
1116 ((f - iv[MIN] ) <? 0);
1119 concaveness2 = concave / (stems.size () - 2);
1123 ugh: this is the a kludge to get
1124 input/regression/beam-concave.ly to behave as
1130 huh? we're dividing twice (which is not scalable) meaning that
1131 the longer the beam, the more unlikely it will be
1132 concave. Maybe you would even expect the other way around??
1137 concaveness2 /= (stems.size () - 2);
1140 /* TODO: some sort of damping iso -> plain horizontal */
1141 if (concaveness1 || concaveness2 > r2)
1143 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1144 Real r = pos.linear_combination (0);
1145 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1146 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1149 return SCM_UNSPECIFIED;
1152 /* This neat trick is by Werner Lemberg,
1153 damped = tanh (slope)
1154 corresponds with some tables in [Wanske] CHECKME */
1155 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1157 Beam::slope_damping (SCM smob)
1159 Grob *me = unsmob_grob (smob);
1161 if (visible_stem_count (me) <= 1)
1162 return SCM_UNSPECIFIED;
1164 SCM s = me->get_grob_property ("damping");
1165 int damping = gh_scm2int (s);
1169 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1170 Real dy = pos.delta ();
1172 Grob *fvs = first_visible_stem (me);
1173 Grob *lvs = last_visible_stem (me);
1175 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1178 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1179 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1180 Real dydx = dy && dx ? dy/dx : 0;
1181 dydx = 0.6 * tanh (dydx) / damping;
1183 Real damped_dy = dydx * dx;
1184 pos[LEFT] += (dy - damped_dy) / 2;
1185 pos[RIGHT] -= (dy - damped_dy) / 2;
1187 me->set_grob_property ("positions", ly_interval2scm (pos));
1189 return SCM_UNSPECIFIED;
1193 Report slice containing the numbers that are both in (car BEAMING)
1197 where_are_the_whole_beams(SCM beaming)
1201 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1203 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1205 l.add_point (gh_scm2int (gh_car (s)));
1211 /* Return the Y position of the stem-end, given the Y-left, Y-right
1212 in POS for stem S. This Y position is relative to S. */
1214 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1216 Interval pos, bool french)
1218 Real beam_translation = get_beam_translation (me);
1221 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1222 Real dy = pos.delta ();
1224 Real stem_y_beam0 = (dy && dx
1229 Direction my_dir = get_grob_direction (s);
1230 SCM beaming = s->get_grob_property ("beaming");
1232 Real stem_y = stem_y_beam0;
1235 Slice bm = where_are_the_whole_beams (beaming);
1236 if (!bm.is_empty ())
1237 stem_y += beam_translation * bm[-my_dir];
1241 Slice bm = Stem::beam_multiplicity(s);
1242 if (!bm.is_empty ())
1243 stem_y +=bm[my_dir] * beam_translation;
1246 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1247 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1253 Hmm. At this time, beam position and slope are determined. Maybe,
1254 stem directions and length should set to relative to the chord's
1255 position of the beam. */
1257 Beam::set_stem_lengths (Grob *me)
1259 Link_array<Grob> stems=
1260 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1266 for (int a = 2; a--;)
1267 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1269 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1270 Real staff_space = Staff_symbol_referencer::staff_space (me);
1274 if (gh_number_p (me->get_grob_property ("gap-count"))
1275 &&gh_scm2int (me->get_grob_property ("gap-count")))
1278 thick = get_thickness(me);
1281 // ugh -> use commonx
1282 Grob * fvs = first_visible_stem (me);
1283 Grob *lvs = last_visible_stem (me);
1285 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1286 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1288 for (int i=0; i < stems.size (); i++)
1291 if (Stem::invisible_b (s))
1294 bool french = to_boolean (s->get_grob_property ("french-beaming"));
1295 Real stem_y = calc_stem_y (me, s, common,
1297 pos, french && s != lvs && s!= fvs);
1300 Make the stems go up to the end of the beam. This doesn't matter
1301 for normal beams, but for tremolo beams it looks silly otherwise.
1304 stem_y += thick * 0.5 * get_grob_direction (s);
1306 Stem::set_stemend (s, 2* stem_y / staff_space);
1311 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1313 Link_array<Grob> stems=
1314 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1317 for (int i=0; i < stems.size (); i++)
1320 Don't overwrite user settings.
1325 /* Don't set beaming for outside of outer stems */
1326 if ((d == LEFT && i == 0)
1327 ||(d == RIGHT && i == stems.size () -1))
1330 Grob *st = stems[i];
1331 SCM beaming_prop = st->get_grob_property ("beaming");
1332 if (beaming_prop == SCM_EOL ||
1333 index_get_cell (beaming_prop, d) == SCM_EOL)
1335 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1337 && i < stems.size() -1
1338 && Stem::invisible_b (st))
1339 b = b <? beaming->infos_.elem(i).beams_i_drul_[-d];
1341 Stem::set_beaming (st, b, d);
1344 while (flip (&d) != LEFT);
1349 Beam::forced_stem_count (Grob *me)
1351 Link_array<Grob>stems =
1352 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1354 for (int i=0; i < stems.size (); i++)
1358 if (Stem::invisible_b (s))
1361 /* I can imagine counting those boundaries as a half forced stem,
1362 but let's count them full for now. */
1363 if (abs (Stem::chord_start_y (s)) > 0.1
1364 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1374 Beam::visible_stem_count (Grob *me)
1376 Link_array<Grob>stems =
1377 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1379 for (int i = stems.size (); i--;)
1381 if (!Stem::invisible_b (stems[i]))
1388 Beam::first_visible_stem (Grob *me)
1390 Link_array<Grob>stems =
1391 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1393 for (int i = 0; i < stems.size (); i++)
1395 if (!Stem::invisible_b (stems[i]))
1402 Beam::last_visible_stem (Grob *me)
1404 Link_array<Grob>stems =
1405 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1406 for (int i = stems.size (); i--;)
1408 if (!Stem::invisible_b (stems[i]))
1418 handle rest under beam (do_post: beams are calculated now)
1419 what about combination of collisions and rest under beam.
1423 rest -> stem -> beam -> interpolate_y_position ()
1425 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1427 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1429 Grob *rest = unsmob_grob (element_smob);
1430 Axis a = (Axis) gh_scm2int (axis);
1432 if (gh_number_p (rest->get_grob_property ("staff-position")))
1433 return gh_int2scm (0);
1435 assert (a == Y_AXIS);
1437 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1440 return gh_double2scm (0.0);
1441 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1443 || !Beam::has_interface (beam)
1444 || !Beam::visible_stem_count (beam))
1445 return gh_double2scm (0.0);
1447 Interval pos (0, 0);
1448 SCM s = beam->get_grob_property ("positions");
1449 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1450 pos = ly_scm2interval (s);
1452 Real dy = pos.delta ();
1453 // ugh -> use commonx
1454 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1455 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1456 Real dydx = dy && dx ? dy/dx : 0;
1458 Direction d = Stem::get_direction (stem);
1459 Real stem_y = (pos[LEFT]
1460 + (stem->relative_coordinate (0, X_AXIS) - x0) * dydx)
1463 Real beam_translation = get_beam_translation (beam);
1464 Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
1465 int beam_count = get_direction_beam_count (beam, d);
1466 Real height_of_my_beams = beam_thickness
1467 + (beam_count - 1) * beam_translation;
1468 Real beam_y = stem_y - height_of_my_beams + beam_thickness / 2.0;
1470 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1472 /* Better calculate relative-distance directly, rather than using
1474 Grob *common_x = rest->common_refpoint (beam, Y_AXIS);
1475 Real rest_dim = rest->extent (common_x, Y_AXIS)[d] / staff_space * d;
1477 Real minimum_distance = robust_scm2double
1478 (rest->get_grob_property ("minimum-beam-collision-distance"), 1);
1480 Real distance = beam_y - rest_dim;
1483 shift = minimum_distance - distance;
1484 else if (minimum_distance > distance)
1485 shift = minimum_distance - distance;
1487 int stafflines = Staff_symbol_referencer::line_count (rest);
1489 /* Always move discretely by half spaces */
1490 Real discrete_shift = ceil (shift * 2.0) / 2.0;
1492 /* Inside staff, move by whole spaces*/
1493 if ((rest->extent (common_x, Y_AXIS)[d] + discrete_shift) * d
1495 ||(rest->extent (common_x, Y_AXIS)[-d] + discrete_shift) * -d
1497 discrete_shift = ceil (discrete_shift);
1499 return gh_double2scm (-d * discrete_shift);
1503 Beam::knee_b (Grob* me)
1505 SCM k = me->get_grob_property ("knee");
1506 if (gh_boolean_p (k))
1507 return gh_scm2bool (k);
1511 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1513 Direction dir = get_grob_direction (unsmob_grob (ly_car (s)));
1522 me->set_grob_property ("knee", gh_bool2scm (knee));
1528 Beam::get_direction_beam_count (Grob *me, Direction d )
1530 Link_array<Grob>stems =
1531 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1534 for (int i = stems.size (); i--;)
1537 Should we take invisible stems into account?
1539 if (Stem::get_direction (stems[i]) == d)
1540 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1547 ADD_INTERFACE (Beam, "beam-interface",
1550 "#'thickness= weight of beams, in staffspace "
1553 "We take the least squares line through the ideal-length stems, and "
1554 "then damp that using "
1556 " damped = tanh (slope) \n"
1558 "this gives an unquantized left and right position for the beam end. "
1559 "Then we take all combinations of quantings near these left and right "
1560 "positions, and give them a score (according to how close they are to "
1561 "the ideal slope, how close the result is to the ideal stems, etc.). We "
1562 "take the best scoring combination. "
1564 "knee positioning-done position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap gap-count chord-tremolo beamed-stem-shorten shorten least-squares-dy damping flag-width-function neutral-direction positions space-function thickness");