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.
17 - the code is littered with * and / staff_space calls for
18 #'positions. Consider moving to real-world coordinates?
20 Problematic issue is user tweaks (user tweaks are in staff-coordinates.)
25 - Stems run to the Y-center of the beam.
27 - beam_translation is the offset between Y centers of the beam.
32 #include <math.h> // tanh.
34 #include "molecule.hh"
35 #include "directional-element-interface.hh"
39 #include "least-squares.hh"
41 #include "paper-def.hh"
43 #include "group-interface.hh"
44 #include "staff-symbol-referencer.hh"
51 bool debug_beam_quanting_flag;
55 #include "text-item.hh" // debug output.
56 #include "font-interface.hh" // debug output.
61 Beam::add_stem (Grob *me, Grob *s)
63 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
65 s->add_dependency (me);
67 assert (!Stem::get_beam (s));
68 s->set_grob_property ("beam", me->self_scm ());
70 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
75 Beam::get_thickness (Grob * me)
77 return robust_scm2double (me->get_grob_property ("thickness"), 0)
78 * Staff_symbol_referencer::staff_space (me);
81 /* Return the translation between 2 adjoining beams. */
83 Beam::get_beam_translation (Grob *me)
85 SCM func = me->get_grob_property ("space-function");
87 if (gh_procedure_p (func))
89 SCM s = gh_call2 (func, me->self_scm (), scm_int2num (get_beam_count (me)));
90 return gh_scm2double (s);
98 /* Maximum beam_count. */
100 Beam::get_beam_count (Grob *me)
103 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
105 Grob *stem = unsmob_grob (ly_car (s));
106 m = m >? (Stem::beam_multiplicity (stem).length () + 1);
113 Space return space between beams.
115 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
117 Beam::space_function (SCM smob, SCM beam_count)
119 Grob *me = unsmob_grob (smob);
121 Real staff_space = Staff_symbol_referencer::staff_space (me);
122 Real line = Staff_symbol_referencer::line_thickness (me);
123 Real thickness = get_thickness (me);
125 Real beam_translation = gh_scm2int (beam_count) < 4
126 ? (2*staff_space + line - thickness) / 2.0
127 : (3*staff_space + line - thickness) / 3.0;
129 return gh_double2scm (beam_translation);
133 /* After pre-processing all directions should be set.
134 Several post-processing routines (stem, slur, script) need stem/beam
136 Currenly, this means that beam has set all stem's directions.
137 [Alternatively, stems could set its own directions, according to
138 their beam, during 'final-pre-processing'.] */
139 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
141 Beam::before_line_breaking (SCM smob)
143 Grob *me = unsmob_grob (smob);
145 /* Beams with less than 2 two stems don't make much sense, but could happen
150 For a beam that only has one stem, we try to do some disappearance magic:
151 we revert the flag, and move on to The Eternal Engraving Fields. */
153 int count = visible_stem_count (me);
156 me->warning (_ ("beam has less than two visible stems"));
158 SCM stems = me->get_grob_property ("stems");
159 if (scm_ilength (stems) == 1)
161 me->warning (_ ("Beam has less than two stems. Removing beam."));
163 unsmob_grob (gh_car (stems))->set_grob_property ("beam", SCM_EOL);
166 return SCM_UNSPECIFIED;
168 else if (scm_ilength (stems) == 0)
171 return SCM_UNSPECIFIED;
176 Direction d = get_default_dir (me);
178 consider_auto_knees (me);
179 set_stem_directions (me, d);
183 set_stem_shorten (me);
191 We want a maximal number of shared beams, but if there is choice, we
192 take the one that is closest to the end of the stem. This is for situations like
205 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
209 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
213 for (int i = lslice[-left_dir];
214 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
217 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
219 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
220 if (scm_memq (scm_int2num (k), left_beaming) != SCM_BOOL_F)
224 if (count >= best_count)
235 Beam::connect_beams (Grob *me)
237 Link_array<Grob> stems=
238 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
241 last_int.set_empty();
242 SCM last_beaming = SCM_EOL;
243 Direction last_dir = CENTER;
244 for (int i = 0; i< stems.size(); i++)
246 Grob *this_stem = stems[i];
247 SCM this_beaming = this_stem->get_grob_property ("beaming");
249 Direction this_dir = get_grob_direction (this_stem);
250 if (gh_pair_p (last_beaming) && gh_pair_p (this_beaming))
252 int start_point = position_with_maximal_common_beams
253 (last_beaming, this_beaming,
260 if (d == RIGHT && i == stems.size()-1)
263 new_slice.set_empty();
264 SCM s = index_get_cell (this_beaming, d);
265 for (; gh_pair_p (s); s = gh_cdr (s))
268 start_point - this_dir * gh_scm2int (gh_car (s));
270 new_slice.add_point (new_beam_pos);
271 gh_set_car_x (s, scm_int2num (new_beam_pos));
276 while (flip (&d) != LEFT);
278 if (!new_slice.is_empty ())
279 last_int = new_slice;
283 gh_set_car_x ( this_beaming, SCM_EOL);
284 SCM s = gh_cdr (this_beaming);
285 for (; gh_pair_p (s); s = gh_cdr (s))
287 int np = - this_dir * gh_scm2int (gh_car(s));
288 gh_set_car_x (s, scm_int2num (np));
289 last_int.add_point (np);
293 if (i == stems.size () -1)
295 gh_set_cdr_x (this_beaming, SCM_EOL);
298 if (scm_ilength (gh_cdr (this_beaming)) > 0)
300 last_beaming = this_beaming;
308 TODO: should not make beams per stem, but per Y-level.
310 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
312 Beam::brew_molecule (SCM grob)
314 Grob *me = unsmob_grob (grob);
317 Link_array<Grob> stems=
318 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
319 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
322 if (visible_stem_count (me))
324 // ugh -> use commonx
325 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
326 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
330 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
331 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
334 SCM posns = me->get_grob_property ("positions");
336 if (!is_number_pair (posns))
338 programming_error ("No beam posns");
339 pos = Interval (0,0);
342 pos= ly_scm2interval (posns);
345 pos *= Staff_symbol_referencer::staff_space (me);
346 Real dy = pos.delta ();
347 Real dydx = (dy && dx) ? dy/dx : 0;
349 Real thick = get_thickness (me);
350 Real bdy = get_beam_translation (me);
352 SCM last_beaming = SCM_EOL;;
353 Real last_xposn = -1;
354 Real last_stem_width = -1 ;
356 Real gap_length =robust_scm2double ( me->get_grob_property ("gap"), 0.0);
359 Real lt = me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
361 for (int i = 0; i<= stems.size(); i++)
363 Grob * st = (i < stems.size()) ? stems[i] : 0;
365 SCM this_beaming = st ? st->get_grob_property ("beaming") : SCM_EOL;
366 Real xposn = st ? st->relative_coordinate (xcommon, X_AXIS) : 0.0;
367 Real stem_width = st ? robust_scm2double (st->get_grob_property ("thickness"), 1.0) *lt : 0 ;
368 Direction stem_dir = st ? to_dir (st->get_grob_property ("direction")) : CENTER;
370 We do the space left of ST, with lfliebertjes pointing to the
371 right from the left stem, and rfliebertjes pointing left from
374 SCM left = (i>0) ? gh_cdr (last_beaming) : SCM_EOL;
375 SCM right = st ? gh_car (this_beaming) : SCM_EOL;
377 Array<int> full_beams;
378 Array<int> lfliebertjes;
379 Array<int> rfliebertjes;
382 gh_pair_p (s); s =gh_cdr (s))
384 int b = gh_scm2int (gh_car (s));
385 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
391 lfliebertjes.push (b);
395 gh_pair_p (s); s =gh_cdr (s))
397 int b = gh_scm2int (gh_car (s));
398 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
400 rfliebertjes.push (b);
405 how much to stick out for beams across linebreaks
407 Real break_overshoot = 3.0;
408 Real w = (i > 0 && st) ? xposn - last_xposn : break_overshoot;
410 Real stem_offset =0.0;
413 w += last_stem_width / 2;
414 stem_offset = -last_stem_width / 2;
421 Real blot = me->get_paper ()->get_realvar (ly_symbol2scm ("blotdiameter"));
422 Molecule whole = Lookup::beam (dydx, w, thick, blot);
426 if (gh_number_p (me->get_grob_property ("gap-count")))
428 gap_count = gh_scm2int (me->get_grob_property ("gap-count"));
429 gapped = Lookup::beam (dydx, w - 2 * gap_length, thick, blot);
431 full_beams.sort (default_compare);
433 full_beams.reverse ();
437 for (int j = full_beams.size (); j--;)
444 b.translate_axis (gap_length, X_AXIS);
446 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
447 b.translate_axis (dydx * (last_xposn - x0) + bdy * full_beams[j], Y_AXIS);
449 the_beam.add_molecule (b);
454 if (lfliebertjes.size() || rfliebertjes.size())
460 int t = Stem::duration_log (st);
462 SCM proc = me->get_grob_property ("flag-width-function");
463 SCM result = gh_call1 (proc, scm_int2num (t));
464 nw_f = gh_scm2double (result);
467 nw_f = break_overshoot;
469 /* Half beam should be one note-width,
470 but let's make sure two half-beams never touch */
471 Real w = (i>0 && st) ? (xposn - last_xposn) : break_overshoot;
474 Molecule half = Lookup::beam (dydx, w, thick, blot);
475 for (int j = lfliebertjes.size(); j--;)
478 b.translate_axis (last_xposn - x0, X_AXIS);
479 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
480 the_beam.add_molecule (b);
482 for (int j = rfliebertjes.size(); j--;)
485 b.translate_axis (xposn - x0 - w , X_AXIS);
486 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
487 the_beam.add_molecule (b);
493 last_stem_width = stem_width;
494 last_beaming = this_beaming;
497 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
498 the_beam.translate_axis (pos[LEFT], Y_AXIS);
501 SCM quant_score = me->get_grob_property ("quant-score");
502 if (debug_beam_quanting_flag
503 && gh_string_p (quant_score))
507 This code prints the demerits for each beam. Perhaps this
508 should be switchable for those who want to twiddle with the
512 SCM properties = Font_interface::font_alist_chain (me);
514 Molecule tm = *unsmob_molecule (Text_item::interpret_markup
515 (me->get_paper ()->self_scm (), properties, quant_score));
516 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0, 0);
522 return the_beam.smobbed_copy();
529 Beam::get_default_dir (Grob *me)
531 Drul_array<int> total;
532 total[UP] = total[DOWN] = 0;
533 Drul_array<int> count;
534 count[UP] = count[DOWN] = 0;
537 Link_array<Grob> stems=
538 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
540 for (int i=0; i <stems.size (); i++)
543 Direction sd = get_grob_direction (s);
545 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
546 int current = sd ? (1 + d * sd)/2 : center_distance;
553 } while (flip (&d) != DOWN);
555 SCM func = me->get_grob_property ("dir-function");
556 SCM s = gh_call2 (func,
557 gh_cons (scm_int2num (count[UP]),
558 scm_int2num (count[DOWN])),
559 gh_cons (scm_int2num (total[UP]),
560 scm_int2num (total[DOWN])));
562 if (gh_number_p (s) && gh_scm2int (s))
565 /* If dir is not determined: get default */
566 return to_dir (me->get_grob_property ("neutral-direction"));
570 /* Set all stems with non-forced direction to beam direction.
571 Urg: non-forced should become `without/with unforced' direction,
572 once stem gets cleaned-up. */
574 Beam::set_stem_directions (Grob *me, Direction d)
576 Link_array<Grob> stems
577 =Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
579 for (int i=0; i <stems.size (); i++)
583 SCM forcedir = s->get_grob_property ("direction");
584 if (!to_dir (forcedir))
585 set_grob_direction (s, d);
590 A union of intervals in the real line.
592 Abysmal performance (quadratic) for large N, hopefully we don't have
593 that large N. In any case, this should probably be rewritten to use
598 Array<Interval> allowed_regions_;
607 allowed_regions_.clear();
610 allowed_regions_.push (s);
613 void remove_interval (Interval rm)
615 for (int i = 0; i < allowed_regions_.size(); )
619 s.intersect (allowed_regions_[i]);
623 Interval before = allowed_regions_[i];
624 Interval after = allowed_regions_[i];
626 before[RIGHT] = s[LEFT];
627 after[LEFT] = s[RIGHT];
629 if (!before.is_empty () && before.length () > 0.0)
631 allowed_regions_.insert (before, i);
634 allowed_regions_.del (i);
635 if (!after.is_empty () && after.length () > 0.0)
637 allowed_regions_.insert (after, i);
649 Only try horizontal beams for knees. No reliable detection of
650 anything else is possible here, since we don't know funky-beaming
651 settings, or X-distances (slopes!) People that want sloped
652 knee-beams, should set the directions manually.
655 Beam::consider_auto_knees (Grob* me)
657 SCM scm = me->get_grob_property ("auto-knee-gap");
658 if (!gh_number_p (scm))
661 Real threshold = gh_scm2double (scm);
667 Link_array<Grob> stems=
668 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
670 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
671 Real staff_space = Staff_symbol_referencer::staff_space (me);
673 Array<Interval> hps_array;
674 for (int i=0; i < stems.size (); i++)
676 Grob* stem = stems[i];
677 if (Stem::invisible_b (stem))
680 Interval hps = Stem::head_positions (stem);
685 hps *= staff_space * 0.5 ;
688 We could subtract beam Y position, but this routine only
689 sets stem directions, a constant shift does not have an
693 hps += stem->relative_coordinate (common, Y_AXIS);
695 if (to_dir (stem->get_grob_property ("direction")))
697 Direction stemdir = to_dir (stem->get_grob_property ("direction"));
698 hps[-stemdir] = - stemdir * infinity_f;
701 hps_array.push (hps);
703 gaps.remove_interval (hps);
707 Real max_gap_len =0.0;
709 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
711 Interval gap = gaps.allowed_regions_[i];
714 the outer gaps are not knees.
716 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
719 if (gap.length () >= max_gap_len)
721 max_gap_len = gap.length();
726 if (max_gap_len > threshold)
729 for (int i = 0; i < stems.size(); i++)
731 Grob* stem = stems[i];
732 if (Stem::invisible_b (stem))
735 Interval hps = hps_array[j++];
738 Direction d = (hps.center () < max_gap.center()) ?
741 stem->set_grob_property ("direction", scm_int2num (d));
743 hps.intersect (max_gap);
744 assert (hps.is_empty () || hps.length () < 1e-6 );
751 /* Set stem's shorten property if unset.
754 take some y-position (chord/beam/nearest?) into account
755 scmify forced-fraction
757 This is done in beam because the shorten has to be uniform over the
762 Beam::set_stem_shorten (Grob *me)
765 shortening looks silly for x staff beams
770 Real forced_fraction = 1.0 * forced_stem_count (me)
771 / visible_stem_count (me);
773 int beam_count = get_beam_count (me);
775 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
776 if (shorten_list == SCM_EOL)
779 Real staff_space = Staff_symbol_referencer::staff_space (me);
782 robust_list_ref (beam_count -1, shorten_list);
783 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
785 /* your similar cute comment here */
786 shorten_f *= forced_fraction;
789 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
792 /* Call list of y-dy-callbacks, that handle setting of
796 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
798 Beam::after_line_breaking (SCM smob)
800 Grob *me = unsmob_grob (smob);
803 return SCM_UNSPECIFIED;
807 Beam::position_beam (Grob *me)
809 if (to_boolean (me->get_grob_property ("positioning-done")))
812 me->set_grob_property ("positioning-done", SCM_BOOL_T);
814 /* Copy to mutable list. */
815 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
816 me->set_grob_property ("positions", s);
818 if (ly_car (s) == SCM_BOOL_F)
820 // one wonders if such genericity is necessary --hwn.
821 SCM callbacks = me->get_grob_property ("position-callbacks");
822 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
823 gh_call1 (ly_car (i), me->self_scm ());
826 set_stem_lengths (me);
831 Compute a first approximation to the beam slope.
833 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
835 Beam::least_squares (SCM smob)
837 Grob *me = unsmob_grob (smob);
839 int count = visible_stem_count (me);
844 me->set_grob_property ("positions", ly_interval2scm (pos));
845 return SCM_UNSPECIFIED;
849 Array<Real> x_posns ;
850 Link_array<Grob> stems=
851 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
852 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
853 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
855 Real my_y = me->relative_coordinate (commony, Y_AXIS);
857 Grob *fvs = first_visible_stem (me);
858 Grob *lvs = last_visible_stem (me);
860 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
861 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
862 Stem::get_stem_info (lvs).ideal_y_
863 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
865 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
866 for (int i=0; i < stems.size (); i++)
870 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
873 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
882 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
883 Stem::chord_start_y (last_visible_stem (me)));
885 /* Simple beams (2 stems) on middle line should be allowed to be
888 However, if both stems reach middle line,
889 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
891 For that case, we apply artificial slope */
892 if (!ideal[LEFT] && chord.delta () && count == 2)
895 Direction d = (Direction) (sign (chord.delta ()) * UP);
896 pos[d] = get_thickness (me) / 2;
905 For broken beams this doesn't work well. In this case, the
906 slope esp. of the first part of a broken beam should predict
907 where the second part goes.
913 Array<Offset> ideals;
914 for (int i=0; i < stems.size (); i++)
917 if (Stem::invisible_b (s))
919 ideals.push (Offset (x_posns[i],
920 Stem::get_stem_info (s).ideal_y_
921 + s->relative_coordinate (commony, Y_AXIS)
925 minimise_least_squares (&dydx, &y, ideals);
928 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
929 pos = Interval (y, (y+dy));
933 "position" is relative to the staff.
935 pos *= 1/ Staff_symbol_referencer::staff_space (me);
937 me->set_grob_property ("positions", ly_interval2scm (pos));
939 return SCM_UNSPECIFIED;
944 We can't combine with previous function, since check concave and
945 slope damping comes first.
947 TODO: we should use the concaveness to control the amount of damping
951 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
953 Beam::shift_region_to_valid (SCM grob)
955 Grob *me = unsmob_grob (grob);
959 Array<Real> x_posns ;
960 Link_array<Grob> stems=
961 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
962 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
963 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
965 Grob *fvs = first_visible_stem (me);
968 return SCM_UNSPECIFIED;
970 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
971 for (int i=0; i < stems.size (); i++)
975 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
979 Grob *lvs = last_visible_stem (me);
981 return SCM_UNSPECIFIED;
983 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
985 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
987 pos *= Staff_symbol_referencer::staff_space (me);
989 Real dy = pos.delta();
995 Shift the positions so that we have a chance of finding good
996 quants (i.e. no short stem failures.)
998 Interval feasible_left_point;
999 feasible_left_point.set_full ();
1000 for (int i=0; i < stems.size (); i++)
1003 if (Stem::invisible_b (s))
1006 Direction d = Stem::get_direction (s);
1009 Stem::get_stem_info (s).shortest_y_
1010 - dydx * x_posns [i];
1013 left_y is now relative to the stem S. We want relative to
1014 ourselves, so translate:
1017 + s->relative_coordinate (commony, Y_AXIS)
1018 - me->relative_coordinate (commony, Y_AXIS);
1024 feasible_left_point.intersect (flp);
1027 if (feasible_left_point.is_empty ())
1029 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1031 else if (!feasible_left_point.contains (y))
1033 if (isinf (feasible_left_point[DOWN]))
1034 y = feasible_left_point[UP] - REGION_SIZE;
1035 else if (isinf (feasible_left_point[UP]))
1036 y = feasible_left_point[DOWN]+ REGION_SIZE;
1038 y = feasible_left_point.center ();
1041 pos = Interval (y, (y+dy));
1042 pos *= 1/ Staff_symbol_referencer::staff_space (me);
1044 me->set_grob_property ("positions", ly_interval2scm (pos));
1045 return SCM_UNSPECIFIED;
1049 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1051 Beam::check_concave (SCM smob)
1053 Grob *me = unsmob_grob (smob);
1055 Link_array<Grob> stems =
1056 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1058 for (int i = 0; i < stems.size ();)
1060 if (Stem::invisible_b (stems[i]))
1066 if (stems.size () < 3)
1067 return SCM_UNSPECIFIED;
1070 /* Concaveness #1: If distance of an inner notehead to line between
1071 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1072 beam is concave (Heinz Stolba).
1074 In the case of knees, the line connecting outer heads is often
1075 not related to the beam slope (it may even go in the other
1076 direction). Skip the check when the outer stems point in
1077 different directions. --hwn
1080 bool concaveness1 = false;
1081 SCM gap = me->get_grob_property ("concaveness-gap");
1082 if (gh_number_p (gap)
1083 && Stem::get_direction(stems.top ())
1084 == Stem::get_direction(stems[0]))
1086 Real r1 = gh_scm2double (gap);
1087 Real dy = Stem::chord_start_y (stems.top ())
1088 - Stem::chord_start_y (stems[0]);
1091 Real slope = dy / (stems.size () - 1);
1093 Real y0 = Stem::chord_start_y (stems[0]);
1094 for (int i = 1; i < stems.size () - 1; i++)
1096 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1099 concaveness1 = true;
1106 /* Concaveness #2: Sum distances of inner noteheads that fall
1107 outside the interval of the two outer noteheads.
1109 We only do this for beams where first and last stem have the same
1113 Note that "convex" stems compensate for "concave" stems.
1114 (is that intentional?) --hwn.
1117 Real concaveness2 = 0;
1118 SCM thresh = me->get_grob_property ("concaveness-threshold");
1119 Real r2 = infinity_f;
1120 if (!concaveness1 && gh_number_p (thresh)
1121 && Stem::get_direction(stems.top ())
1122 == Stem::get_direction(stems[0]))
1124 r2 = gh_scm2double (thresh);
1126 Direction dir = Stem::get_direction(stems.top ());
1128 Interval iv (Stem::chord_start_y (stems[0]),
1129 Stem::chord_start_y (stems.top ()));
1131 if (iv[MAX] < iv[MIN])
1134 for (int i = 1; i < stems.size () - 1; i++)
1136 Real f = Stem::chord_start_y (stems[i]);
1137 concave += ((f - iv[MAX] ) >? 0) +
1138 ((f - iv[MIN] ) <? 0);
1141 concaveness2 = concave / (stems.size () - 2);
1145 ugh: this is the a kludge to get
1146 input/regression/beam-concave.ly to behave as
1152 huh? we're dividing twice (which is not scalable) meaning that
1153 the longer the beam, the more unlikely it will be
1154 concave. Maybe you would even expect the other way around??
1159 concaveness2 /= (stems.size () - 2);
1162 /* TODO: some sort of damping iso -> plain horizontal */
1163 if (concaveness1 || concaveness2 > r2)
1165 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1166 Real r = pos.linear_combination (0);
1168 r /= Staff_symbol_referencer::staff_space (me);
1169 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1170 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1173 return SCM_UNSPECIFIED;
1176 /* This neat trick is by Werner Lemberg,
1177 damped = tanh (slope)
1178 corresponds with some tables in [Wanske] CHECKME */
1179 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1181 Beam::slope_damping (SCM smob)
1183 Grob *me = unsmob_grob (smob);
1185 if (visible_stem_count (me) <= 1)
1186 return SCM_UNSPECIFIED;
1188 SCM s = me->get_grob_property ("damping");
1189 int damping = gh_scm2int (s);
1193 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1194 pos *= Staff_symbol_referencer::staff_space (me);
1196 Real dy = pos.delta ();
1198 Grob *fvs = first_visible_stem (me);
1199 Grob *lvs = last_visible_stem (me);
1201 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1204 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1205 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1206 Real dydx = dy && dx ? dy/dx : 0;
1207 dydx = 0.6 * tanh (dydx) / damping;
1209 Real damped_dy = dydx * dx;
1210 pos[LEFT] += (dy - damped_dy) / 2;
1211 pos[RIGHT] -= (dy - damped_dy) / 2;
1213 pos *= 1/ Staff_symbol_referencer::staff_space (me);
1215 me->set_grob_property ("positions", ly_interval2scm (pos));
1217 return SCM_UNSPECIFIED;
1221 Report slice containing the numbers that are both in (car BEAMING)
1225 where_are_the_whole_beams(SCM beaming)
1229 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1231 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1233 l.add_point (gh_scm2int (gh_car (s)));
1239 /* Return the Y position of the stem-end, given the Y-left, Y-right
1240 in POS for stem S. This Y position is relative to S. */
1242 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1244 Interval pos, bool french)
1246 Real beam_translation = get_beam_translation (me);
1249 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1250 Real dy = pos.delta ();
1252 Real stem_y_beam0 = (dy && dx
1257 Direction my_dir = get_grob_direction (s);
1258 SCM beaming = s->get_grob_property ("beaming");
1260 Real stem_y = stem_y_beam0;
1263 Slice bm = where_are_the_whole_beams (beaming);
1264 if (!bm.is_empty ())
1265 stem_y += beam_translation * bm[-my_dir];
1269 Slice bm = Stem::beam_multiplicity(s);
1270 if (!bm.is_empty ())
1271 stem_y +=bm[my_dir] * beam_translation;
1274 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1275 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1281 Hmm. At this time, beam position and slope are determined. Maybe,
1282 stem directions and length should set to relative to the chord's
1283 position of the beam. */
1285 Beam::set_stem_lengths (Grob *me)
1287 Link_array<Grob> stems=
1288 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1294 for (int a = 2; a--;)
1295 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1297 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1298 Real staff_space = Staff_symbol_referencer::staff_space (me);
1303 if (gh_number_p (me->get_grob_property ("gap-count"))
1304 &&gh_scm2int (me->get_grob_property ("gap-count")))
1307 thick = get_thickness(me);
1310 // ugh -> use commonx
1311 Grob * fvs = first_visible_stem (me);
1312 Grob *lvs = last_visible_stem (me);
1314 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1315 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1317 for (int i=0; i < stems.size (); i++)
1320 if (Stem::invisible_b (s))
1323 bool french = to_boolean (s->get_grob_property ("french-beaming"));
1324 Real stem_y = calc_stem_y (me, s, common,
1326 pos, french && s != lvs && s!= fvs);
1329 Make the stems go up to the end of the beam. This doesn't matter
1330 for normal beams, but for tremolo beams it looks silly otherwise.
1333 stem_y += thick * 0.5 * get_grob_direction (s);
1335 Stem::set_stemend (s, 2* stem_y / staff_space);
1340 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1342 Link_array<Grob> stems=
1343 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1346 for (int i=0; i < stems.size (); i++)
1349 Don't overwrite user settings.
1354 /* Don't set beaming for outside of outer stems */
1355 if ((d == LEFT && i == 0)
1356 ||(d == RIGHT && i == stems.size () -1))
1359 Grob *st = stems[i];
1360 SCM beaming_prop = st->get_grob_property ("beaming");
1361 if (beaming_prop == SCM_EOL ||
1362 index_get_cell (beaming_prop, d) == SCM_EOL)
1364 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1366 && i < stems.size() -1
1367 && Stem::invisible_b (st))
1368 b = b <? beaming->infos_.elem(i).beams_i_drul_[-d];
1370 Stem::set_beaming (st, b, d);
1373 while (flip (&d) != LEFT);
1378 Beam::forced_stem_count (Grob *me)
1380 Link_array<Grob>stems =
1381 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1383 for (int i=0; i < stems.size (); i++)
1387 if (Stem::invisible_b (s))
1390 /* I can imagine counting those boundaries as a half forced stem,
1391 but let's count them full for now. */
1392 if (abs (Stem::chord_start_y (s)) > 0.1
1393 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1403 Beam::visible_stem_count (Grob *me)
1405 Link_array<Grob>stems =
1406 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1408 for (int i = stems.size (); i--;)
1410 if (!Stem::invisible_b (stems[i]))
1417 Beam::first_visible_stem (Grob *me)
1419 Link_array<Grob>stems =
1420 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1422 for (int i = 0; i < stems.size (); i++)
1424 if (!Stem::invisible_b (stems[i]))
1431 Beam::last_visible_stem (Grob *me)
1433 Link_array<Grob>stems =
1434 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1435 for (int i = stems.size (); i--;)
1437 if (!Stem::invisible_b (stems[i]))
1447 handle rest under beam (do_post: beams are calculated now)
1448 what about combination of collisions and rest under beam.
1452 rest -> stem -> beam -> interpolate_y_position ()
1454 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1456 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1458 Grob *rest = unsmob_grob (element_smob);
1459 Axis a = (Axis) gh_scm2int (axis);
1461 if (gh_number_p (rest->get_grob_property ("staff-position")))
1462 return gh_int2scm (0);
1464 assert (a == Y_AXIS);
1466 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1469 return gh_double2scm (0.0);
1470 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1472 || !Beam::has_interface (beam)
1473 || !Beam::visible_stem_count (beam))
1474 return gh_double2scm (0.0);
1476 Interval pos (0, 0);
1477 SCM s = beam->get_grob_property ("positions");
1478 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1479 pos = ly_scm2interval (s);
1480 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1485 Real dy = pos.delta ();
1487 // ugh -> use commonx
1488 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1489 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1490 Real dydx = dy && dx ? dy/dx : 0;
1492 Direction d = Stem::get_direction (stem);
1493 Real stem_y = pos[LEFT] + (stem->relative_coordinate (0, X_AXIS) - x0) * dydx;
1495 Real beam_translation = get_beam_translation (beam);
1496 Real beam_thickness = Beam::get_thickness (beam);
1498 int beam_count = get_direction_beam_count (beam, d);
1499 Real height_of_my_beams = beam_thickness / 2
1500 + (beam_count - 1) * beam_translation;
1501 Real beam_y = stem_y - d * height_of_my_beams;
1503 Grob *common_y = rest->common_refpoint (beam, Y_AXIS);
1505 Real rest_dim = rest->extent (common_y, Y_AXIS)[d];
1506 Real minimum_distance =
1507 staff_space * robust_scm2double (rest->get_grob_property ("minimum-distance"), 0.0);
1509 Real shift = d * (((beam_y - d * minimum_distance) - rest_dim) * d <? 0.0);
1511 shift /= staff_space;
1512 Real rad = Staff_symbol_referencer::line_count (rest) * staff_space / 2;
1514 /* Always move discretely by half spaces */
1515 shift = ceil (fabs (shift * 2.0)) / 2.0 * sign (shift);
1517 /* Inside staff, move by whole spaces*/
1518 if ((rest->extent (common_y, Y_AXIS)[d] + staff_space * shift) * d
1520 || (rest->extent (common_y, Y_AXIS)[-d] + staff_space * shift) * -d
1522 shift = ceil (fabs (shift)) *sign (shift);
1524 return gh_double2scm (staff_space * shift);
1528 Beam::knee_b (Grob* me)
1530 SCM k = me->get_grob_property ("knee");
1531 if (gh_boolean_p (k))
1532 return gh_scm2bool (k);
1536 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1538 Direction dir = get_grob_direction (unsmob_grob (ly_car (s)));
1547 me->set_grob_property ("knee", gh_bool2scm (knee));
1553 Beam::get_direction_beam_count (Grob *me, Direction d )
1555 Link_array<Grob>stems =
1556 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1559 for (int i = stems.size (); i--;)
1562 Should we take invisible stems into account?
1564 if (Stem::get_direction (stems[i]) == d)
1565 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1572 ADD_INTERFACE (Beam, "beam-interface",
1575 "#'thickness= weight of beams, in staffspace "
1578 "We take the least squares line through the ideal-length stems, and "
1579 "then damp that using "
1581 " damped = tanh (slope) \n"
1583 "this gives an unquantized left and right position for the beam end. "
1584 "Then we take all combinations of quantings near these left and right "
1585 "positions, and give them a score (according to how close they are to "
1586 "the ideal slope, how close the result is to the ideal stems, etc.). We "
1587 "take the best scoring combination. "
1589 "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");