2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
13 - Determine auto knees based on positions if it's set by the user.
15 - the code is littered with * and / staff_space calls for
16 #'positions. Consider moving to real-world coordinates?
18 Problematic issue is user tweaks (user tweaks are in staff-coordinates.)
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"
46 bool debug_beam_quanting_flag;
50 #include "text-item.hh" // debug output.
51 #include "font-interface.hh" // debug output.
56 Beam::add_stem (Grob *me, Grob *s)
58 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
60 s->add_dependency (me);
62 assert (!Stem::get_beam (s));
63 s->set_grob_property ("beam", me->self_scm ());
65 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
70 Beam::get_thickness (Grob * me)
72 return robust_scm2double (me->get_grob_property ("thickness"), 0)
73 * Staff_symbol_referencer::staff_space (me);
76 /* Return the translation between 2 adjoining beams. */
78 Beam::get_beam_translation (Grob *me)
80 SCM func = me->get_grob_property ("space-function");
82 if (gh_procedure_p (func))
84 SCM s = gh_call2 (func, me->self_scm (), scm_int2num (get_beam_count (me)));
85 return gh_scm2double (s);
93 /* Maximum beam_count. */
95 Beam::get_beam_count (Grob *me)
98 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
100 Grob *stem = unsmob_grob (ly_car (s));
101 m = m >? (Stem::beam_multiplicity (stem).length () + 1);
108 Space return space between beams.
110 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
112 Beam::space_function (SCM smob, SCM beam_count)
114 Grob *me = unsmob_grob (smob);
116 Real staff_space = Staff_symbol_referencer::staff_space (me);
117 Real line = Staff_symbol_referencer::line_thickness (me);
118 Real thickness = get_thickness (me);
120 Real beam_translation = gh_scm2int (beam_count) < 4
121 ? (2*staff_space + line - thickness) / 2.0
122 : (3*staff_space + line - thickness) / 3.0;
124 return gh_double2scm (beam_translation);
128 /* After pre-processing all directions should be set.
129 Several post-processing routines (stem, slur, script) need stem/beam
131 Currenly, this means that beam has set all stem's directions.
132 [Alternatively, stems could set its own directions, according to
133 their beam, during 'final-pre-processing'.] */
134 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
136 Beam::before_line_breaking (SCM smob)
138 Grob *me = unsmob_grob (smob);
140 /* Beams with less than 2 two stems don't make much sense, but could happen
145 For a beam that only has one stem, we try to do some disappearance magic:
146 we revert the flag, and move on to The Eternal Engraving Fields. */
148 int count = visible_stem_count (me);
151 me->warning (_ ("beam has less than two visible stems"));
153 SCM stems = me->get_grob_property ("stems");
154 if (scm_ilength (stems) == 1)
156 me->warning (_ ("Beam has less than two stems. Removing beam."));
158 unsmob_grob (gh_car (stems))->set_grob_property ("beam", SCM_EOL);
161 return SCM_UNSPECIFIED;
163 else if (scm_ilength (stems) == 0)
166 return SCM_UNSPECIFIED;
171 Direction d = get_default_dir (me);
173 consider_auto_knees (me);
174 set_stem_directions (me, d);
178 set_stem_shorten (me);
186 We want a maximal number of shared beams, but if there is choice, we
187 take the one that is closest to the end of the stem. This is for situations like
200 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
204 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
208 for (int i = lslice[-left_dir];
209 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
212 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
214 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
215 if (scm_memq (scm_int2num (k), left_beaming) != SCM_BOOL_F)
219 if (count >= best_count)
230 Beam::connect_beams (Grob *me)
232 Link_array<Grob> stems=
233 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
236 last_int.set_empty();
237 SCM last_beaming = SCM_EOL;
238 Direction last_dir = CENTER;
239 for (int i = 0; i< stems.size(); i++)
241 Grob *this_stem = stems[i];
242 SCM this_beaming = this_stem->get_grob_property ("beaming");
244 Direction this_dir = get_grob_direction (this_stem);
245 if (gh_pair_p (last_beaming) && gh_pair_p (this_beaming))
247 int start_point = position_with_maximal_common_beams
248 (last_beaming, this_beaming,
255 if (d == RIGHT && i == stems.size()-1)
258 new_slice.set_empty();
259 SCM s = index_get_cell (this_beaming, d);
260 for (; gh_pair_p (s); s = gh_cdr (s))
263 start_point - this_dir * gh_scm2int (gh_car (s));
265 new_slice.add_point (new_beam_pos);
266 gh_set_car_x (s, scm_int2num (new_beam_pos));
271 while (flip (&d) != LEFT);
273 if (!new_slice.is_empty ())
274 last_int = new_slice;
278 gh_set_car_x ( this_beaming, SCM_EOL);
279 SCM s = gh_cdr (this_beaming);
280 for (; gh_pair_p (s); s = gh_cdr (s))
282 int np = - this_dir * gh_scm2int (gh_car(s));
283 gh_set_car_x (s, scm_int2num (np));
284 last_int.add_point (np);
288 if (i == stems.size () -1)
290 gh_set_cdr_x (this_beaming, SCM_EOL);
293 if (scm_ilength (gh_cdr (this_beaming)) > 0)
295 last_beaming = this_beaming;
303 TODO: should not make beams per stem, but per Y-level.
305 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
307 Beam::brew_molecule (SCM grob)
309 Grob *me = unsmob_grob (grob);
312 Link_array<Grob> stems=
313 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
314 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
317 if (visible_stem_count (me))
319 // ugh -> use commonx
320 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
321 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
325 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
326 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
329 SCM posns = me->get_grob_property ("positions");
330 Drul_array<Real> pos;
331 if (!is_number_pair (posns))
333 programming_error ("No beam posns");
334 pos = Interval (0,0);
337 pos= ly_scm2realdrul (posns);
339 scale_drul ( &pos, Staff_symbol_referencer::staff_space (me));
341 Real dy = pos[RIGHT] - pos[LEFT];
342 Real dydx = (dy && dx) ? dy/dx : 0;
344 Real thick = get_thickness (me);
345 Real bdy = get_beam_translation (me);
347 SCM last_beaming = SCM_EOL;
348 Real last_xposn = -1;
349 Real last_stem_width = -1 ;
351 Real gap_length =robust_scm2double ( me->get_grob_property ("gap"), 0.0);
354 Real lt = me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
356 for (int i = 0; i<= stems.size(); i++)
358 Grob * st = (i < stems.size()) ? stems[i] : 0;
360 SCM this_beaming = st ? st->get_grob_property ("beaming") : SCM_EOL;
361 Real xposn = st ? st->relative_coordinate (xcommon, X_AXIS) : 0.0;
362 Real stem_width = st ? robust_scm2double (st->get_grob_property ("thickness"), 1.0) *lt : 0 ;
363 Direction stem_dir = st ? to_dir (st->get_grob_property ("direction")) : CENTER;
365 We do the space left of ST, with lfliebertjes pointing to the
366 right from the left stem, and rfliebertjes pointing left from
369 SCM left = (i>0) ? gh_cdr (last_beaming) : SCM_EOL;
370 SCM right = st ? gh_car (this_beaming) : SCM_EOL;
372 Array<int> full_beams;
373 Array<int> lfliebertjes;
374 Array<int> rfliebertjes;
377 gh_pair_p (s); s =gh_cdr (s))
379 int b = gh_scm2int (gh_car (s));
380 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
386 lfliebertjes.push (b);
390 gh_pair_p (s); s =gh_cdr (s))
392 int b = gh_scm2int (gh_car (s));
393 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
395 rfliebertjes.push (b);
400 how much to stick out for beams across linebreaks
402 Real break_overshoot = 3.0;
403 Real w = (i > 0 && st) ? xposn - last_xposn : break_overshoot;
405 Real stem_offset =0.0;
408 w += last_stem_width / 2;
409 stem_offset = -last_stem_width / 2;
416 Real blot = me->get_paper ()->get_realvar (ly_symbol2scm ("blotdiameter"));
417 Molecule whole = Lookup::beam (dydx, w, thick, blot);
421 if (gh_number_p (me->get_grob_property ("gap-count")))
423 gap_count = gh_scm2int (me->get_grob_property ("gap-count"));
424 gapped = Lookup::beam (dydx, w - 2 * gap_length, thick, blot);
426 full_beams.sort (default_compare);
428 full_beams.reverse ();
432 for (int j = full_beams.size (); j--;)
439 b.translate_axis (gap_length, X_AXIS);
441 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
442 b.translate_axis (dydx * (last_xposn - x0) + bdy * full_beams[j], Y_AXIS);
444 the_beam.add_molecule (b);
449 if (lfliebertjes.size() || rfliebertjes.size())
455 int t = Stem::duration_log (st);
457 SCM proc = me->get_grob_property ("flag-width-function");
458 SCM result = gh_call1 (proc, scm_int2num (t));
459 nw_f = gh_scm2double (result);
462 nw_f = break_overshoot;
464 /* Half beam should be one note-width,
465 but let's make sure two half-beams never touch */
466 Real w = (i>0 && st) ? (xposn - last_xposn) : break_overshoot;
469 Molecule half = Lookup::beam (dydx, w, thick, blot);
470 for (int j = lfliebertjes.size(); j--;)
473 b.translate_axis (last_xposn - x0, X_AXIS);
474 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
475 the_beam.add_molecule (b);
477 for (int j = rfliebertjes.size(); j--;)
480 b.translate_axis (xposn - x0 - w , X_AXIS);
481 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
482 the_beam.add_molecule (b);
488 last_stem_width = stem_width;
489 last_beaming = this_beaming;
492 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
493 the_beam.translate_axis (pos[LEFT], Y_AXIS);
496 SCM quant_score = me->get_grob_property ("quant-score");
497 if (debug_beam_quanting_flag
498 && gh_string_p (quant_score))
502 This code prints the demerits for each beam. Perhaps this
503 should be switchable for those who want to twiddle with the
507 SCM properties = Font_interface::font_alist_chain (me);
509 Molecule tm = *unsmob_molecule (Text_item::interpret_markup
510 (me->get_paper ()->self_scm (), properties, quant_score));
511 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0, 0);
517 return the_beam.smobbed_copy();
524 Beam::get_default_dir (Grob *me)
526 Drul_array<int> total;
527 total[UP] = total[DOWN] = 0;
528 Drul_array<int> count;
529 count[UP] = count[DOWN] = 0;
532 Link_array<Grob> stems=
533 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
535 for (int i=0; i <stems.size (); i++)
538 Direction sd = get_grob_direction (s);
540 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
541 int current = sd ? (1 + d * sd)/2 : center_distance;
548 } while (flip (&d) != DOWN);
550 SCM func = me->get_grob_property ("dir-function");
551 SCM s = gh_call2 (func,
552 gh_cons (scm_int2num (count[UP]),
553 scm_int2num (count[DOWN])),
554 gh_cons (scm_int2num (total[UP]),
555 scm_int2num (total[DOWN])));
557 if (gh_number_p (s) && gh_scm2int (s))
560 /* If dir is not determined: get default */
561 return to_dir (me->get_grob_property ("neutral-direction"));
565 /* Set all stems with non-forced direction to beam direction.
566 Urg: non-forced should become `without/with unforced' direction,
567 once stem gets cleaned-up. */
569 Beam::set_stem_directions (Grob *me, Direction d)
571 Link_array<Grob> stems
572 =Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
574 for (int i=0; i <stems.size (); i++)
578 SCM forcedir = s->get_grob_property ("direction");
579 if (!to_dir (forcedir))
580 set_grob_direction (s, d);
585 A union of intervals in the real line.
587 Abysmal performance (quadratic) for large N, hopefully we don't have
588 that large N. In any case, this should probably be rewritten to use
593 Array<Interval> allowed_regions_;
602 allowed_regions_.clear();
605 allowed_regions_.push (s);
608 void remove_interval (Interval rm)
610 for (int i = 0; i < allowed_regions_.size(); )
614 s.intersect (allowed_regions_[i]);
618 Interval before = allowed_regions_[i];
619 Interval after = allowed_regions_[i];
621 before[RIGHT] = s[LEFT];
622 after[LEFT] = s[RIGHT];
624 if (!before.is_empty () && before.length () > 0.0)
626 allowed_regions_.insert (before, i);
629 allowed_regions_.del (i);
630 if (!after.is_empty () && after.length () > 0.0)
632 allowed_regions_.insert (after, i);
644 Only try horizontal beams for knees. No reliable detection of
645 anything else is possible here, since we don't know funky-beaming
646 settings, or X-distances (slopes!) People that want sloped
647 knee-beams, should set the directions manually.
650 Beam::consider_auto_knees (Grob* me)
652 SCM scm = me->get_grob_property ("auto-knee-gap");
653 if (!gh_number_p (scm))
656 Real threshold = gh_scm2double (scm);
662 Link_array<Grob> stems=
663 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
665 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
666 Real staff_space = Staff_symbol_referencer::staff_space (me);
668 Array<Interval> hps_array;
669 for (int i=0; i < stems.size (); i++)
671 Grob* stem = stems[i];
672 if (Stem::invisible_b (stem))
675 Interval hps = Stem::head_positions (stem);
680 hps *= staff_space * 0.5 ;
683 We could subtract beam Y position, but this routine only
684 sets stem directions, a constant shift does not have an
688 hps += stem->relative_coordinate (common, Y_AXIS);
690 if (to_dir (stem->get_grob_property ("direction")))
692 Direction stemdir = to_dir (stem->get_grob_property ("direction"));
693 hps[-stemdir] = - stemdir * infinity_f;
696 hps_array.push (hps);
698 gaps.remove_interval (hps);
702 Real max_gap_len =0.0;
704 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
706 Interval gap = gaps.allowed_regions_[i];
709 the outer gaps are not knees.
711 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
714 if (gap.length () >= max_gap_len)
716 max_gap_len = gap.length();
721 if (max_gap_len > threshold)
724 for (int i = 0; i < stems.size(); i++)
726 Grob* stem = stems[i];
727 if (Stem::invisible_b (stem))
730 Interval hps = hps_array[j++];
733 Direction d = (hps.center () < max_gap.center()) ?
736 stem->set_grob_property ("direction", scm_int2num (d));
738 hps.intersect (max_gap);
739 assert (hps.is_empty () || hps.length () < 1e-6 );
746 /* Set stem's shorten property if unset.
749 take some y-position (chord/beam/nearest?) into account
750 scmify forced-fraction
752 This is done in beam because the shorten has to be uniform over the
757 Beam::set_stem_shorten (Grob *me)
760 shortening looks silly for x staff beams
765 Real forced_fraction = 1.0 * forced_stem_count (me)
766 / visible_stem_count (me);
768 int beam_count = get_beam_count (me);
770 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
771 if (shorten_list == SCM_EOL)
774 Real staff_space = Staff_symbol_referencer::staff_space (me);
777 robust_list_ref (beam_count -1, shorten_list);
778 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
780 /* your similar cute comment here */
781 shorten_f *= forced_fraction;
784 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
787 /* Call list of y-dy-callbacks, that handle setting of
791 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
793 Beam::after_line_breaking (SCM smob)
795 Grob *me = unsmob_grob (smob);
798 return SCM_UNSPECIFIED;
802 Beam::position_beam (Grob *me)
804 if (to_boolean (me->get_grob_property ("positioning-done")))
807 me->set_grob_property ("positioning-done", SCM_BOOL_T);
809 /* Copy to mutable list. */
810 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
811 me->set_grob_property ("positions", s);
813 if (ly_car (s) == SCM_BOOL_F)
815 // one wonders if such genericity is necessary --hwn.
816 SCM callbacks = me->get_grob_property ("position-callbacks");
817 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
818 gh_call1 (ly_car (i), me->self_scm ());
821 set_stem_lengths (me);
826 Compute a first approximation to the beam slope.
828 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
830 Beam::least_squares (SCM smob)
832 Grob *me = unsmob_grob (smob);
834 int count = visible_stem_count (me);
839 me->set_grob_property ("positions", ly_interval2scm (pos));
840 return SCM_UNSPECIFIED;
844 Array<Real> x_posns ;
845 Link_array<Grob> stems=
846 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
847 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
848 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
850 Real my_y = me->relative_coordinate (commony, Y_AXIS);
852 Grob *fvs = first_visible_stem (me);
853 Grob *lvs = last_visible_stem (me);
855 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
856 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
857 Stem::get_stem_info (lvs).ideal_y_
858 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
860 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
861 for (int i=0; i < stems.size (); i++)
865 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
868 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
877 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
878 Stem::chord_start_y (last_visible_stem (me)));
880 /* Simple beams (2 stems) on middle line should be allowed to be
883 However, if both stems reach middle line,
884 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
886 For that case, we apply artificial slope */
887 if (!ideal[LEFT] && chord.delta () && count == 2)
890 Direction d = (Direction) (sign (chord.delta ()) * UP);
891 pos[d] = get_thickness (me) / 2;
900 For broken beams this doesn't work well. In this case, the
901 slope esp. of the first part of a broken beam should predict
902 where the second part goes.
904 me->set_grob_property ("least-squares-dy",
905 gh_double2scm (pos[RIGHT] - pos[LEFT]));
909 Array<Offset> ideals;
910 for (int i=0; i < stems.size (); i++)
913 if (Stem::invisible_b (s))
915 ideals.push (Offset (x_posns[i],
916 Stem::get_stem_info (s).ideal_y_
917 + s->relative_coordinate (commony, Y_AXIS)
921 minimise_least_squares (&dydx, &y, ideals);
924 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
925 pos = Interval (y, (y+dy));
929 "position" is relative to the staff.
931 scale_drul (&pos, 1/ Staff_symbol_referencer::staff_space (me));
933 me->set_grob_property ("positions", ly_interval2scm (pos));
935 return SCM_UNSPECIFIED;
940 We can't combine with previous function, since check concave and
941 slope damping comes first.
943 TODO: we should use the concaveness to control the amount of damping
947 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
949 Beam::shift_region_to_valid (SCM grob)
951 Grob *me = unsmob_grob (grob);
955 Array<Real> x_posns ;
956 Link_array<Grob> stems=
957 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
958 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
959 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
961 Grob *fvs = first_visible_stem (me);
964 return SCM_UNSPECIFIED;
966 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
967 for (int i=0; i < stems.size (); i++)
971 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
975 Grob *lvs = last_visible_stem (me);
977 return SCM_UNSPECIFIED;
979 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
981 Drul_array<Real> pos = ly_scm2interval ( me->get_grob_property ("positions"));
983 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
985 Real dy = pos[RIGHT] - pos[LEFT];
991 Shift the positions so that we have a chance of finding good
992 quants (i.e. no short stem failures.)
994 Interval feasible_left_point;
995 feasible_left_point.set_full ();
996 for (int i=0; i < stems.size (); i++)
999 if (Stem::invisible_b (s))
1002 Direction d = Stem::get_direction (s);
1005 Stem::get_stem_info (s).shortest_y_
1006 - dydx * x_posns [i];
1009 left_y is now relative to the stem S. We want relative to
1010 ourselves, so translate:
1013 + s->relative_coordinate (commony, Y_AXIS)
1014 - me->relative_coordinate (commony, Y_AXIS);
1020 feasible_left_point.intersect (flp);
1023 if (feasible_left_point.is_empty ())
1025 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1027 else if (!feasible_left_point.contains (y))
1029 if (isinf (feasible_left_point[DOWN]))
1030 y = feasible_left_point[UP] - REGION_SIZE;
1031 else if (isinf (feasible_left_point[UP]))
1032 y = feasible_left_point[DOWN]+ REGION_SIZE;
1034 y = feasible_left_point.center ();
1037 pos = Drul_array<Real> (y, (y+dy));
1038 scale_drul (&pos, 1/ Staff_symbol_referencer::staff_space (me));
1040 me->set_grob_property ("positions", ly_interval2scm (pos));
1041 return SCM_UNSPECIFIED;
1045 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1047 Beam::check_concave (SCM smob)
1049 Grob *me = unsmob_grob (smob);
1051 Link_array<Grob> stems =
1052 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1054 for (int i = 0; i < stems.size ();)
1056 if (Stem::invisible_b (stems[i]))
1062 if (stems.size () < 3)
1063 return SCM_UNSPECIFIED;
1066 /* Concaveness #1: If distance of an inner notehead to line between
1067 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1068 beam is concave (Heinz Stolba).
1070 In the case of knees, the line connecting outer heads is often
1071 not related to the beam slope (it may even go in the other
1072 direction). Skip the check when the outer stems point in
1073 different directions. --hwn
1076 bool concaveness1 = false;
1077 SCM gap = me->get_grob_property ("concaveness-gap");
1078 if (gh_number_p (gap)
1079 && Stem::get_direction(stems.top ())
1080 == Stem::get_direction(stems[0]))
1082 Real r1 = gh_scm2double (gap);
1083 Real dy = Stem::chord_start_y (stems.top ())
1084 - Stem::chord_start_y (stems[0]);
1087 Real slope = dy / (stems.size () - 1);
1089 Real y0 = Stem::chord_start_y (stems[0]);
1090 for (int i = 1; i < stems.size () - 1; i++)
1092 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1095 concaveness1 = true;
1102 /* Concaveness #2: Sum distances of inner noteheads that fall
1103 outside the interval of the two outer noteheads.
1105 We only do this for beams where first and last stem have the same
1109 Note that "convex" stems compensate for "concave" stems.
1110 (is that intentional?) --hwn.
1113 Real concaveness2 = 0;
1114 SCM thresh = me->get_grob_property ("concaveness-threshold");
1115 Real r2 = infinity_f;
1116 if (!concaveness1 && gh_number_p (thresh)
1117 && Stem::get_direction(stems.top ())
1118 == Stem::get_direction(stems[0]))
1120 r2 = gh_scm2double (thresh);
1122 Direction dir = Stem::get_direction(stems.top ());
1124 Interval iv (Stem::chord_start_y (stems[0]),
1125 Stem::chord_start_y (stems.top ()));
1127 if (iv[MAX] < iv[MIN])
1130 for (int i = 1; i < stems.size () - 1; i++)
1132 Real f = Stem::chord_start_y (stems[i]);
1133 concave += ((f - iv[MAX] ) >? 0) +
1134 ((f - iv[MIN] ) <? 0);
1137 concaveness2 = concave / (stems.size () - 2);
1141 ugh: this is the a kludge to get
1142 input/regression/beam-concave.ly to behave as
1148 huh? we're dividing twice (which is not scalable) meaning that
1149 the longer the beam, the more unlikely it will be
1150 concave. Maybe you would even expect the other way around??
1155 concaveness2 /= (stems.size () - 2);
1158 /* TODO: some sort of damping iso -> plain horizontal */
1159 if (concaveness1 || concaveness2 > r2)
1161 Drul_array<Real> pos = ly_scm2interval (me->get_grob_property ("positions"));
1162 Real r = linear_combination (pos, 0);
1164 r /= Staff_symbol_referencer::staff_space (me);
1165 me->set_grob_property ("positions", ly_interval2scm (Drul_array<Real> (r, r)));
1166 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1169 return SCM_UNSPECIFIED;
1172 /* This neat trick is by Werner Lemberg,
1173 damped = tanh (slope)
1174 corresponds with some tables in [Wanske] CHECKME */
1175 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1177 Beam::slope_damping (SCM smob)
1179 Grob *me = unsmob_grob (smob);
1181 if (visible_stem_count (me) <= 1)
1182 return SCM_UNSPECIFIED;
1184 SCM s = me->get_grob_property ("damping");
1185 int damping = gh_scm2int (s);
1189 Drul_array<Real> pos = ly_scm2interval (me->get_grob_property ("positions"));
1190 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
1192 Real dy = pos[RIGHT] - pos[LEFT];
1194 Grob *fvs = first_visible_stem (me);
1195 Grob *lvs = last_visible_stem (me);
1197 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1200 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1201 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1202 Real dydx = dy && dx ? dy/dx : 0;
1203 dydx = 0.6 * tanh (dydx) / damping;
1205 Real damped_dy = dydx * dx;
1206 pos[LEFT] += (dy - damped_dy) / 2;
1207 pos[RIGHT] -= (dy - damped_dy) / 2;
1209 scale_drul (&pos, 1/Staff_symbol_referencer::staff_space (me));
1211 me->set_grob_property ("positions", ly_interval2scm (pos));
1213 return SCM_UNSPECIFIED;
1217 Report slice containing the numbers that are both in (car BEAMING)
1221 where_are_the_whole_beams(SCM beaming)
1225 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1227 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1229 l.add_point (gh_scm2int (gh_car (s)));
1235 /* Return the Y position of the stem-end, given the Y-left, Y-right
1236 in POS for stem S. This Y position is relative to S. */
1238 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1240 Drul_array<Real> pos, bool french)
1242 Real beam_translation = get_beam_translation (me);
1245 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1246 Real dy = pos[RIGHT] - pos[LEFT];
1248 Real stem_y_beam0 = (dy && dx
1253 Direction my_dir = get_grob_direction (s);
1254 SCM beaming = s->get_grob_property ("beaming");
1256 Real stem_y = stem_y_beam0;
1259 Slice bm = where_are_the_whole_beams (beaming);
1260 if (!bm.is_empty ())
1261 stem_y += beam_translation * bm[-my_dir];
1265 Slice bm = Stem::beam_multiplicity(s);
1266 if (!bm.is_empty ())
1267 stem_y +=bm[my_dir] * beam_translation;
1270 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1271 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1277 Hmm. At this time, beam position and slope are determined. Maybe,
1278 stem directions and length should set to relative to the chord's
1279 position of the beam. */
1281 Beam::set_stem_lengths (Grob *me)
1283 Link_array<Grob> stems=
1284 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1290 for (int a = 2; a--;)
1291 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1293 Drul_array<Real> pos = ly_scm2realdrul (me->get_grob_property ("positions"));
1294 Real staff_space = Staff_symbol_referencer::staff_space (me);
1295 scale_drul (&pos, staff_space);
1299 if (gh_number_p (me->get_grob_property ("gap-count"))
1300 &&gh_scm2int (me->get_grob_property ("gap-count")))
1303 thick = get_thickness(me);
1306 // ugh -> use commonx
1307 Grob * fvs = first_visible_stem (me);
1308 Grob *lvs = last_visible_stem (me);
1310 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1311 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1313 for (int i=0; i < stems.size (); i++)
1316 if (Stem::invisible_b (s))
1319 bool french = to_boolean (s->get_grob_property ("french-beaming"));
1320 Real stem_y = calc_stem_y (me, s, common,
1322 pos, french && s != lvs && s!= fvs);
1325 Make the stems go up to the end of the beam. This doesn't matter
1326 for normal beams, but for tremolo beams it looks silly otherwise.
1329 stem_y += thick * 0.5 * get_grob_direction (s);
1331 Stem::set_stemend (s, 2* stem_y / staff_space);
1336 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1338 Link_array<Grob> stems=
1339 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1342 for (int i=0; i < stems.size (); i++)
1345 Don't overwrite user settings.
1350 /* Don't set beaming for outside of outer stems */
1351 if ((d == LEFT && i == 0)
1352 ||(d == RIGHT && i == stems.size () -1))
1355 Grob *st = stems[i];
1356 SCM beaming_prop = st->get_grob_property ("beaming");
1357 if (beaming_prop == SCM_EOL ||
1358 index_get_cell (beaming_prop, d) == SCM_EOL)
1360 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1362 && i < stems.size() -1
1363 && Stem::invisible_b (st))
1364 b = b <? beaming->infos_.elem(i).beams_i_drul_[-d];
1366 Stem::set_beaming (st, b, d);
1369 while (flip (&d) != LEFT);
1374 Beam::forced_stem_count (Grob *me)
1376 Link_array<Grob>stems =
1377 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1379 for (int i=0; i < stems.size (); i++)
1383 if (Stem::invisible_b (s))
1386 /* I can imagine counting those boundaries as a half forced stem,
1387 but let's count them full for now. */
1388 if (abs (Stem::chord_start_y (s)) > 0.1
1389 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1399 Beam::visible_stem_count (Grob *me)
1401 Link_array<Grob>stems =
1402 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1404 for (int i = stems.size (); i--;)
1406 if (!Stem::invisible_b (stems[i]))
1413 Beam::first_visible_stem (Grob *me)
1415 Link_array<Grob>stems =
1416 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1418 for (int i = 0; i < stems.size (); i++)
1420 if (!Stem::invisible_b (stems[i]))
1427 Beam::last_visible_stem (Grob *me)
1429 Link_array<Grob>stems =
1430 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1431 for (int i = stems.size (); i--;)
1433 if (!Stem::invisible_b (stems[i]))
1443 handle rest under beam (do_post: beams are calculated now)
1444 what about combination of collisions and rest under beam.
1448 rest -> stem -> beam -> interpolate_y_position ()
1450 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1452 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1454 Grob *rest = unsmob_grob (element_smob);
1455 Axis a = (Axis) gh_scm2int (axis);
1457 if (gh_number_p (rest->get_grob_property ("staff-position")))
1458 return gh_int2scm (0);
1460 assert (a == Y_AXIS);
1462 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1465 return gh_double2scm (0.0);
1466 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1468 || !Beam::has_interface (beam)
1469 || !Beam::visible_stem_count (beam))
1470 return gh_double2scm (0.0);
1472 Drul_array<Real> pos (0, 0);
1473 SCM s = beam->get_grob_property ("positions");
1474 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1475 pos = ly_scm2interval (s);
1476 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1478 scale_drul (&pos, staff_space);
1481 Real dy = pos[RIGHT] - pos[LEFT];
1483 // ugh -> use commonx
1484 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1485 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1486 Real dydx = dy && dx ? dy/dx : 0;
1488 Direction d = Stem::get_direction (stem);
1489 Real stem_y = pos[LEFT] + (stem->relative_coordinate (0, X_AXIS) - x0) * dydx;
1491 Real beam_translation = get_beam_translation (beam);
1492 Real beam_thickness = Beam::get_thickness (beam);
1494 int beam_count = get_direction_beam_count (beam, d);
1495 Real height_of_my_beams = beam_thickness / 2
1496 + (beam_count - 1) * beam_translation;
1497 Real beam_y = stem_y - d * height_of_my_beams;
1499 Grob *common_y = rest->common_refpoint (beam, Y_AXIS);
1501 Real rest_dim = rest->extent (common_y, Y_AXIS)[d];
1502 Real minimum_distance =
1503 staff_space * robust_scm2double (rest->get_grob_property ("minimum-distance"), 0.0);
1505 Real shift = d * (((beam_y - d * minimum_distance) - rest_dim) * d <? 0.0);
1507 shift /= staff_space;
1508 Real rad = Staff_symbol_referencer::line_count (rest) * staff_space / 2;
1510 /* Always move discretely by half spaces */
1511 shift = ceil (fabs (shift * 2.0)) / 2.0 * sign (shift);
1513 /* Inside staff, move by whole spaces*/
1514 if ((rest->extent (common_y, Y_AXIS)[d] + staff_space * shift) * d
1516 || (rest->extent (common_y, Y_AXIS)[-d] + staff_space * shift) * -d
1518 shift = ceil (fabs (shift)) *sign (shift);
1520 return gh_double2scm (staff_space * shift);
1524 Beam::knee_b (Grob* me)
1526 SCM k = me->get_grob_property ("knee");
1527 if (gh_boolean_p (k))
1528 return gh_scm2bool (k);
1532 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1534 Direction dir = get_grob_direction (unsmob_grob (ly_car (s)));
1543 me->set_grob_property ("knee", gh_bool2scm (knee));
1549 Beam::get_direction_beam_count (Grob *me, Direction d )
1551 Link_array<Grob>stems =
1552 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1555 for (int i = stems.size (); i--;)
1558 Should we take invisible stems into account?
1560 if (Stem::get_direction (stems[i]) == d)
1561 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1568 ADD_INTERFACE (Beam, "beam-interface",
1571 "#'thickness= weight of beams, in staffspace "
1574 "We take the least squares line through the ideal-length stems, and "
1575 "then damp that using "
1577 " damped = tanh (slope) \n"
1579 "this gives an unquantized left and right position for the beam end. "
1580 "Then we take all combinations of quantings near these left and right "
1581 "positions, and give them a score (according to how close they are to "
1582 "the ideal slope, how close the result is to the ideal stems, etc.). We "
1583 "take the best scoring combination. "
1585 "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");