2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
18 * Remove #'direction from beam. A beam has no direction per se.
19 It may only set directions for stems.
23 * Use Number_pair i.s.o Interval to represent (yl, yr).
28 #include <math.h> // tanh.
30 #include "molecule.hh"
31 #include "directional-element-interface.hh"
35 #include "least-squares.hh"
37 #include "paper-def.hh"
39 #include "group-interface.hh"
40 #include "staff-symbol-referencer.hh"
44 #include "text-item.hh" // debug output.
45 #include "font-interface.hh" // debug output.
48 #define DEBUG_QUANTING 0
52 shrink_extra_weight (Real x)
54 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
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::beam_l (s));
65 s->set_grob_property ("beam", me->self_scm ());
67 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
72 TODO: fix this for grace notes.
75 Beam::get_interbeam (Grob *me)
77 Real slt = me->paper_l ()->get_var ("stafflinethickness");
78 Real ss = Staff_symbol_referencer::staff_space (me);
79 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
82 int multiplicity = get_multiplicity (me);
83 Real interbeam = multiplicity < 4
84 ? (2*ss + slt - thickness) / 2.0
85 : (3*ss + slt - thickness) / 3.0;
91 Beam::get_multiplicity (Grob *me)
94 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
96 Grob *sc = unsmob_grob (ly_car (s));
98 if (Stem::has_interface (sc))
99 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
104 /* After pre-processing all directions should be set.
105 Several post-processing routines (stem, slur, script) need stem/beam
107 Currenly, this means that beam has set all stem's directions.
108 [Alternatively, stems could set its own directions, according to
109 their beam, during 'final-pre-processing'.] */
110 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
112 Beam::before_line_breaking (SCM smob)
114 Grob *me = unsmob_grob (smob);
116 /* Beams with less than 2 two stems don't make much sense, but could happen
121 For a beam that only has one stem, we try to do some disappearance magic:
122 we revert the flag, and move on to The Eternal Engraving Fields. */
124 int count = visible_stem_count (me);
127 me->warning (_ ("beam has less than two visible stems"));
129 SCM stems = me->get_grob_property ("stems");
130 if (scm_ilength (stems) == 1)
132 me->warning (_ ("Beam has less than two stems. Removing beam."));
134 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
137 return SCM_UNSPECIFIED;
139 else if (scm_ilength (stems) == 0)
142 return SCM_UNSPECIFIED;
147 if (!Directional_element_interface::get (me))
148 Directional_element_interface::set (me, get_default_dir (me));
150 consider_auto_knees (me);
151 set_stem_directions (me);
152 set_stem_shorten (me);
158 Beam::get_default_dir (Grob *me)
160 Drul_array<int> total;
161 total[UP] = total[DOWN] = 0;
162 Drul_array<int> count;
163 count[UP] = count[DOWN] = 0;
166 Link_array<Item> stems=
167 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
169 for (int i=0; i <stems.size (); i++)
172 Direction sd = Directional_element_interface::get (s);
173 int current = sd ? (1 + d * sd)/2
174 : Stem::get_center_distance (s, (Direction)-d);
181 } while (flip (&d) != DOWN);
183 SCM func = me->get_grob_property ("dir-function");
184 SCM s = gh_call2 (func,
185 gh_cons (gh_int2scm (count[UP]),
186 gh_int2scm (count[DOWN])),
187 gh_cons (gh_int2scm (total[UP]),
188 gh_int2scm (total[DOWN])));
190 if (gh_number_p (s) && gh_scm2int (s))
193 /* If dir is not determined: get default */
194 return to_dir (me->get_grob_property ("neutral-direction"));
198 /* Set all stems with non-forced direction to beam direction.
199 Urg: non-forced should become `without/with unforced' direction,
200 once stem gets cleaned-up. */
202 Beam::set_stem_directions (Grob *me)
204 Link_array<Item> stems
205 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
206 Direction d = Directional_element_interface::get (me);
208 for (int i=0; i <stems.size (); i++)
211 SCM force = s->remove_grob_property ("dir-forced");
212 if (!gh_boolean_p (force) || !gh_scm2bool (force))
213 Directional_element_interface::set (s, d);
217 /* Simplistic auto-knees; only consider vertical gap between two
220 `Forced' stem directions are ignored. If you don't want auto-knees,
221 don't set, or unset auto-knee-gap. */
223 Beam::consider_auto_knees (Grob *me)
225 SCM scm = me->get_grob_property ("auto-knee-gap");
227 if (gh_number_p (scm))
231 Real staff_space = Staff_symbol_referencer::staff_space (me);
232 Real gap = gh_scm2double (scm) / staff_space;
234 Direction d = Directional_element_interface::get (me);
235 Link_array<Item> stems=
236 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
238 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
239 for (int i=1; i < stems.size (); i++)
240 if (!Stem::invisible_b (stems[i]))
241 common = common->common_refpoint (stems[i], Y_AXIS);
244 for (int i=1; i < stems.size (); i++)
246 if (!Stem::invisible_b (stems[i-1]))
248 if (Stem::invisible_b (stems[l]))
250 if (Stem::invisible_b (stems[i]))
253 Real left = Stem::extremal_heads (stems[l])[d]
254 ->relative_coordinate (common, Y_AXIS);
255 Real right = Stem::extremal_heads (stems[i])[-d]
256 ->relative_coordinate (common, Y_AXIS);
258 Real dy = right - left;
262 knee_y = (right + left) / 2;
270 for (int i=0; i < stems.size (); i++)
272 if (Stem::invisible_b (stems[i]))
275 Real y = Stem::extremal_heads (stems[i])[d]
276 ->relative_coordinate (common, Y_AXIS);
278 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
279 s->set_grob_property ("dir-forced", SCM_BOOL_T);
285 /* Set stem's shorten property if unset.
288 take some y-position (chord/beam/nearest?) into account
289 scmify forced-fraction
293 why is shorten stored in beam, and not directly in stem?
297 Beam::set_stem_shorten (Grob *m)
299 Spanner*me = dynamic_cast<Spanner*> (m);
301 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
303 int multiplicity = get_multiplicity (me);
305 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
306 if (shorten == SCM_EOL)
309 int sz = scm_ilength (shorten);
311 Real staff_space = Staff_symbol_referencer::staff_space (me);
312 SCM shorten_elt = scm_list_ref (shorten,
313 gh_int2scm (multiplicity <? (sz - 1)));
314 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
316 /* your similar cute comment here */
317 shorten_f *= forced_fraction;
319 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
322 /* Call list of y-dy-callbacks, that handle setting of
323 grob-properties y, dy.
325 User may set grob-properties: y-position-hs and height-hs
326 (to be fixed) that override the calculated y and dy.
328 Because y and dy cannot be calculated and quanted separately, we
329 always calculate both, then check for user override. */
330 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
332 Beam::after_line_breaking (SCM smob)
334 Grob *me = unsmob_grob (smob);
336 /* Copy to mutable list. */
337 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
338 me->set_grob_property ("positions", s);
340 if (ly_car (s) != SCM_BOOL_F)
341 return SCM_UNSPECIFIED;
343 SCM callbacks = me->get_grob_property ("position-callbacks");
344 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
345 gh_call1 (ly_car (i), smob);
347 return SCM_UNSPECIFIED;
361 - Make all demerits customisable
363 - One sensible check per demerit (what's this --hwn)
365 - Add demerits for quants per se, as to forbid a specific quant
369 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
371 Beam::quanting (SCM smob)
373 Grob *me = unsmob_grob (smob);
375 SCM s = me->get_grob_property ("positions");
376 Real yl = gh_scm2double (gh_car (s));
377 Real yr = gh_scm2double (gh_cdr (s));
379 Real ss = Staff_symbol_referencer::staff_space (me);
380 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
381 Real slt = me->paper_l ()->get_var ("stafflinethickness") / ss;
384 SCM sdy = me->get_grob_property ("least-squares-dy");
385 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
388 Real sit = (thickness - slt) / 2;
390 Real hang = 1.0 - (thickness - slt) / 2;
391 Real quants [] = {straddle, sit, inter, hang };
393 int num_quants = int (sizeof (quants)/sizeof (Real));
398 going to REGION_SIZE == 2, yields another 0.6 second with
402 (result indexes between 70 and 575) ? --hwn.
406 const int REGION_SIZE = 3;
407 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
408 for (int j = 0; j < num_quants; j++)
410 quantsl.push (i + quants[j] + int (yl));
411 quantsr.push (i + quants[j] + int (yr));
414 Array<Quant_score> qscores;
416 for (int l =0; l < quantsl.size (); l++)
417 for (int r =0; r < quantsr.size (); r++)
429 This is a longish function, but we don't separate this out into
430 neat modular separate subfunctions, as the subfunctions would be
431 called for many values of YL, YR. By precomputing various
432 parameters outside of the loop, we can save a lot of time.
435 for (int i = qscores.size (); i--;)
436 if (qscores[i].demerits < 100)
439 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
443 Real rad = Staff_symbol_referencer::staff_radius (me);
444 int multiplicity = get_multiplicity (me);
445 Real interbeam = multiplicity < 4
446 ? (2*ss + slt - thickness) / 2.0
447 : (3*ss + slt - thickness) / 3.0;
449 for (int i = qscores.size (); i--;)
450 if (qscores[i].demerits < 100)
453 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
454 rad, slt, thickness, interbeam,
460 Do stem lengths. These depend on YL and YR linearly, so we can
461 precompute for every stem 2 factors.
463 Link_array<Grob> stems=
464 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
465 Array<Stem_info> stem_infos;
466 Array<Real> lbase_lengths;
467 Array<Real> rbase_lengths;
469 Array<int> directions;
470 for (int i= 0; i < stems.size(); i++)
473 stem_infos.push( Stem::calc_stem_info (s));
475 Real b = calc_stem_y (me, s, Interval (1,0));
476 lbase_lengths.push (b);
478 b = calc_stem_y (me, s, Interval (0,1));
479 rbase_lengths.push (b);
480 directions.push( Directional_element_interface::get( s));
483 for (int i = qscores.size (); i--;)
484 if (qscores[i].demerits < 100)
487 += score_stem_lengths (stems, stem_infos,
488 lbase_lengths, rbase_lengths,
490 me, qscores[i].yl, qscores[i].yr);
496 for (int i = qscores.size (); i--;)
498 if (qscores[i].demerits < best)
500 best = qscores [i].demerits ;
506 me->set_grob_property ("positions",
507 gh_cons (gh_double2scm (qscores[best_idx].yl),
508 gh_double2scm (qscores[best_idx].yr))
514 me->set_grob_property ("quant-score",
515 gh_double2scm (qscores[best_idx].demerits));
516 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
519 return SCM_UNSPECIFIED;
523 Beam::score_stem_lengths (Link_array<Grob>stems,
524 Array<Stem_info> stem_infos,
525 Array<Real> left_factor,
526 Array<Real> right_factor,
527 Array<int> directions,
528 Grob*me, Real yl, Real yr)
530 Real demerit_score = 0.0 ;
532 for (int i=0; i < stems.size (); i++)
535 if (Stem::invisible_b (s))
539 yl * left_factor[i] + right_factor[i]* yr;
541 Stem_info info = stem_infos[i];
542 Direction d = Direction (directions[i]);
544 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
545 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
547 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
550 demerit_score *= 2.0 /stems.size ();
552 return demerit_score;
556 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
557 Real dy_mus, Real dy_damp)
562 if (sign (dy_damp) != sign (dy))
567 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
570 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
577 return x - floor (x);
581 Beam::score_forbidden_quants (Grob*me,
585 Real thickness, Real interbeam,
591 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
593 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
596 // todo: use multiplicity of outer stems.
597 if (multiplicity >= 2)
601 Real sit = (thickness - slt) / 2;
603 Real hang = 1.0 - (thickness - slt) / 2;
605 Direction dir = Directional_element_interface::get (me);
606 if (fabs (yl - dir * interbeam) < rad
607 && fabs (my_modf (yl) - inter) < 1e-3)
609 if (fabs (yr - dir * interbeam) < rad
610 && fabs (my_modf (yr) - inter) < 1e-3)
616 Can't we simply compute the distance between the nearest
617 staffline and the secondary beam? That would get rid of the
618 silly case analysis here (which is probably not when we have
619 different beam-thicknesses.)
624 // hmm, without Interval/Drul_array, you get ~ 4x same code...
625 if (fabs (yl - dir * interbeam) < rad + inter)
627 if (dir == UP && dy <= eps
628 && fabs (my_modf (yl) - sit) < eps)
631 if (dir == DOWN && dy >= eps
632 && fabs (my_modf (yl) - hang) < eps)
636 if (fabs (yr - dir * interbeam) < rad + inter)
638 if (dir == UP && dy >= eps
639 && fabs (my_modf (yr) - sit) < eps)
642 if (dir == DOWN && dy <= eps
643 && fabs (my_modf (yr) - hang) < eps)
647 if (multiplicity >= 3)
649 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
651 if (dir == UP && dy <= eps
652 && fabs (my_modf (yl) - straddle) < eps)
655 if (dir == DOWN && dy >= eps
656 && fabs (my_modf (yl) - straddle) < eps)
660 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
662 if (dir == UP && dy >= eps
663 && fabs (my_modf (yr) - straddle) < eps)
666 if (dir == DOWN && dy <= eps
667 && fabs (my_modf (yr) - straddle) < eps)
678 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
680 Beam::least_squares (SCM smob)
682 Grob *me = unsmob_grob (smob);
684 int count = visible_stem_count (me);
689 me->set_grob_property ("positions", ly_interval2scm (pos));
690 return SCM_UNSPECIFIED;
693 Direction dir = Directional_element_interface::get (me);
695 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
696 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
700 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
701 Stem::chord_start_f (last_visible_stem (me)));
705 TODO : use scoring for this.
707 complicated, because we take stem-info.ideal for determining
711 /* Make simple beam on middle line have small tilt */
712 if (!ideal[LEFT] && chord.delta () && count == 2)
714 Direction d = (Direction) (sign (chord.delta ()) * dir);
715 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
728 Array<Offset> ideals;
730 // ugh -> use commonx
731 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
732 Link_array<Item> stems=
733 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
735 for (int i=0; i < stems.size (); i++)
738 if (Stem::invisible_b (s))
740 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
741 Stem::calc_stem_info (s).ideal_y));
745 minimise_least_squares (&dydx, &y, ideals);
747 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
749 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
751 pos = Interval (y*dir, (y+dy) * dir);
754 me->set_grob_property ("positions", ly_interval2scm (pos));
755 return SCM_UNSPECIFIED;
758 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
760 Beam::check_concave (SCM smob)
762 Grob *me = unsmob_grob (smob);
764 Link_array<Item> stems =
765 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
767 for (int i = 0; i < stems.size ();)
769 if (Stem::invisible_b (stems[i]))
775 if (stems.size () < 3)
776 return SCM_UNSPECIFIED;
778 /* Concaveness try #2: Sum distances of inner noteheads that
779 fall outside the interval of the two outer noteheads */
781 Interval iv (Stem::chord_start_f (stems[0]),
782 Stem::chord_start_f (stems.top ()));
784 if (iv[MAX] < iv[MIN])
787 for (int i = 1; i < stems.size () - 1; i++)
790 Real f = Stem::chord_start_f (stems[i]);
791 if ((c = f - iv[MAX]) > 0)
793 else if ((c = f - iv[MIN]) < 0)
797 Direction dir = Directional_element_interface::get (me);
800 Real concaveness = concave / (stems.size () - 2);
801 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
802 to behave as baerenreiter. */
803 concaveness /= (stems.size () - 2);
805 Real r = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
807 /* TODO: some sort of damping iso -> plain horizontal */
810 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
811 Real r = pos.linear_combination (0);
812 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
813 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
816 return SCM_UNSPECIFIED;
819 /* This neat trick is by Werner Lemberg,
820 damped = tanh (slope)
821 corresponds with some tables in [Wanske] CHECKME */
822 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
824 Beam::slope_damping (SCM smob)
826 Grob *me = unsmob_grob (smob);
828 if (visible_stem_count (me) <= 1)
829 return SCM_UNSPECIFIED;
831 SCM s = me->get_grob_property ("damping");
832 int damping = gh_scm2int (s);
836 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
837 Real dy = pos.delta ();
839 // ugh -> use commonx
840 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
841 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
842 Real dydx = dy && dx ? dy/dx : 0;
843 dydx = 0.6 * tanh (dydx) / damping;
845 Real damped_dy = dydx * dx;
846 pos[LEFT] += (dy - damped_dy) / 2;
847 pos[RIGHT] -= (dy - damped_dy) / 2;
849 me->set_grob_property ("positions", ly_interval2scm (pos));
851 return SCM_UNSPECIFIED;
854 MAKE_SCHEME_CALLBACK (Beam, end_after_line_breaking, 1);
856 Beam::end_after_line_breaking (SCM smob)
858 Grob *me = unsmob_grob (smob);
859 set_stem_lengths (me);
861 return SCM_UNSPECIFIED;
865 Calculate the Y position of the stem-end, given the Y-left, Y-right
866 in POS, and for stem S.
869 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
871 int beam_multiplicity = get_multiplicity (me);
872 int stem_multiplicity = (Stem::flag_i (s) - 2) >? 0;
874 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
875 Real interbeam = get_interbeam (me);
877 // ugh -> use commonx
878 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
879 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
880 Real dy = pos.delta ();
881 Real stem_y = (dy && dx
882 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
887 Direction dir = Directional_element_interface::get (me);
888 Direction sdir = Directional_element_interface::get (s);
893 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
895 // huh, why not for first visible?
897 Grob *last_visible = last_visible_stem (me);
900 if ( Staff_symbol_referencer::staff_symbol_l (s)
901 != Staff_symbol_referencer::staff_symbol_l (last_visible))
902 stem_y += Directional_element_interface::get (me)
903 * (beam_multiplicity - stem_multiplicity) * interbeam;
906 programming_error ("No last visible stem");
913 Hmm. At this time, beam position and slope are determined. Maybe,
914 stem directions and length should set to relative to the chord's
915 position of the beam. */
917 Beam::set_stem_lengths (Grob *me)
919 Link_array<Item> stems=
920 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
922 if (stems.size () <= 1)
925 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
926 for (int i=1; i < stems.size (); i++)
927 if (!Stem::invisible_b (stems[i]))
928 common = common->common_refpoint (stems[i], Y_AXIS);
930 Direction dir = Directional_element_interface::get (me);
931 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
932 Real staff_space = Staff_symbol_referencer::staff_space (me);
933 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
934 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
935 for (int i=0; i < stems.size (); i++)
938 if (Stem::invisible_b (s))
941 Real stem_y = calc_stem_y (me, s, pos);
943 // doesn't play well with dvips
945 if (Stem::get_direction (s) == dir)
946 stem_y += Stem::get_direction (s) * thick / 2;
948 /* caution: stem measures in staff-positions */
949 Real id = me->relative_coordinate (common, Y_AXIS)
950 - stems[i]->relative_coordinate (common, Y_AXIS);
951 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
956 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
958 Link_array<Grob> stems=
959 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
962 for (int i=0; i < stems.size (); i++)
966 /* Don't overwrite user override (?) */
967 if (Stem::beam_count (stems[i], d) == -1
968 /* Don't set beaming for outside of outer stems */
969 && ! (d == LEFT && i == 0)
970 && ! (d == RIGHT && i == stems.size () -1))
972 int b = beaming->infos_.elem (i).beams_i_drul_[d];
973 Stem::set_beaming (stems[i], b, d);
976 while (flip (&d) != LEFT);
983 beams to go with one stem.
988 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
990 // ugh -> use commonx
992 && ! (next->relative_coordinate (0, X_AXIS)
993 > here->relative_coordinate (0, X_AXIS)))
995 && ! (prev->relative_coordinate (0, X_AXIS)
996 < here->relative_coordinate (0, X_AXIS))))
997 programming_error ("Beams are not left-to-right");
999 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1000 Real bdy = get_interbeam (me);
1003 Molecule rightbeams;
1006 if (!Stem::first_head (here))
1009 int t = Stem::type_i (here);
1011 SCM proc = me->get_grob_property ("flag-width-function");
1012 SCM result = gh_call1 (proc, gh_int2scm (t));
1013 nw_f = gh_scm2double (result);
1017 Direction dir = Directional_element_interface::get (me);
1019 /* [Tremolo] beams on whole notes may not have direction set? */
1021 dir = Directional_element_interface::get (here);
1024 /* half beams extending to the left. */
1027 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1028 - Stem::beam_count (prev, RIGHT);
1029 int lwholebeams= Stem::beam_count (here, LEFT)
1030 <? Stem::beam_count (prev, RIGHT);
1032 /* Half beam should be one note-width,
1033 but let's make sure two half-beams never touch */
1035 // FIXME: TODO (check) stem width / sloped beams
1036 Real w = here->relative_coordinate (0, X_AXIS)
1037 - prev->relative_coordinate (0, X_AXIS);
1038 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1040 * me->paper_l ()->get_var ("stafflinethickness");
1044 if (lhalfs) // generates warnings if not
1045 a = Lookup::beam (dydx, w + stem_w, thick);
1046 a.translate (Offset (-w, -w * dydx));
1047 a.translate_axis (-stem_w/2, X_AXIS);
1048 for (int j = 0; j < lhalfs; j++)
1051 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1052 leftbeams.add_molecule (b);
1058 int rhalfs = Stem::beam_count (here, RIGHT)
1059 - Stem::beam_count (next, LEFT);
1060 int rwholebeams= Stem::beam_count (here, RIGHT)
1061 <? Stem::beam_count (next, LEFT);
1063 Real w = next->relative_coordinate (0, X_AXIS)
1064 - here->relative_coordinate (0, X_AXIS);
1066 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1068 * me->paper_l ()->get_var ("stafflinethickness");
1070 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1071 a.translate_axis (- stem_w/2, X_AXIS);
1075 SCM gap = me->get_grob_property ("gap");
1076 if (gh_number_p (gap))
1078 int gap_i = gh_scm2int ((gap));
1079 int nogap = rwholebeams - gap_i;
1081 for (; j < nogap; j++)
1084 b.translate_axis (-dir * bdy * j, Y_AXIS);
1085 rightbeams.add_molecule (b);
1087 if (Stem::invisible_b (here))
1092 a = Lookup::beam (dydx, w + stem_w, thick);
1095 for (; j < rwholebeams; j++)
1099 if (Stem::invisible_b (here))
1100 // ugh, see chord-tremolo.ly
1101 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1104 b.translate (Offset (tx, -dir * bdy * j));
1105 rightbeams.add_molecule (b);
1110 a = Lookup::beam (dydx, w, thick);
1112 for (; j < rwholebeams + rhalfs; j++)
1115 b.translate_axis (- dir * bdy * j, Y_AXIS);
1116 rightbeams.add_molecule (b);
1120 leftbeams.add_molecule (rightbeams);
1122 /* Does beam quanting think of the asymetry of beams?
1123 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1128 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1130 Beam::brew_molecule (SCM smob)
1132 Grob *me =unsmob_grob (smob);
1135 if (!gh_pair_p (me->get_grob_property ("stems")))
1138 Link_array<Item>stems =
1139 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1140 if (visible_stem_count (me))
1142 // ugh -> use commonx
1143 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1144 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1148 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1149 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1152 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1153 Real dy = pos.delta ();
1154 Real dydx = dy && dx ? dy/dx : 0;
1156 for (int i=0; i < stems.size (); i++)
1158 Item *item = stems[i];
1159 Item *prev = (i > 0)? stems[i-1] : 0;
1160 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1162 Molecule sb = stem_beams (me, item, next, prev, dydx);
1163 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1164 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1165 mol.add_molecule (sb);
1168 mol.translate_axis (x0
1169 - dynamic_cast<Spanner*> (me)
1170 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1176 This code prints the demerits for each beam. Perhaps this
1177 should be switchable for those who want to twiddle with the
1183 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1186 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1189 SCM properties = Font_interface::font_alist_chain (me);
1191 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1192 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1195 return mol.smobbed_copy ();
1199 Beam::forced_stem_count (Grob *me)
1201 Link_array<Item>stems =
1202 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1204 for (int i=0; i < stems.size (); i++)
1208 if (Stem::invisible_b (s))
1211 if (((int)Stem::chord_start_f (s))
1212 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1222 Beam::visible_stem_count (Grob *me)
1224 Link_array<Item>stems =
1225 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1227 for (int i = stems.size (); i--;)
1229 if (!Stem::invisible_b (stems[i]))
1236 Beam::first_visible_stem (Grob *me)
1238 Link_array<Item>stems =
1239 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1241 for (int i = 0; i < stems.size (); i++)
1243 if (!Stem::invisible_b (stems[i]))
1250 Beam::last_visible_stem (Grob *me)
1252 Link_array<Item>stems =
1253 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1254 for (int i = stems.size (); i--;)
1256 if (!Stem::invisible_b (stems[i]))
1266 handle rest under beam (do_post: beams are calculated now)
1267 what about combination of collisions and rest under beam.
1271 rest -> stem -> beam -> interpolate_y_position ()
1273 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1275 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1277 Grob *rest = unsmob_grob (element_smob);
1278 Axis a = (Axis) gh_scm2int (axis);
1280 assert (a == Y_AXIS);
1282 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1285 return gh_double2scm (0.0);
1286 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1288 || !Beam::has_interface (beam)
1289 || !Beam::visible_stem_count (beam))
1290 return gh_double2scm (0.0);
1292 // make callback for rest from this.
1293 // todo: make sure this calced already.
1295 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1296 Interval pos (0, 0);
1297 SCM s = beam->get_grob_property ("positions");
1298 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1299 pos = ly_scm2interval (s);
1301 Real dy = pos.delta ();
1302 // ugh -> use commonx
1303 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1304 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1305 Real dydx = dy && dx ? dy/dx : 0;
1307 Direction d = Stem::get_direction (stem);
1308 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1310 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1313 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1316 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1318 minimum_dist + -d * (beamy - rest_dim) >? 0;
1320 int stafflines = Staff_symbol_referencer::line_count (rest);
1322 // move discretely by half spaces.
1323 int discrete_dist = int (ceil (dist));
1325 // move by whole spaces inside the staff.
1326 if (discrete_dist < stafflines+1)
1327 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1329 return gh_double2scm (-d * discrete_dist);
1334 Beam::has_interface (Grob *me)
1336 return me->has_interface (ly_symbol2scm ("beam-interface"));
1340 ADD_INTERFACE (Beam, "beam-interface",
1343 #'thickness= weight of beams, in staffspace
1346 We take the least squares line through the ideal-length stems, and
1347 then damp that using
1349 damped = tanh (slope)
1351 this gives an unquantized left and right position for the beam end.
1352 Then we take all combinations of quantings near these left and right
1353 positions, and give them a score (according to how close they are to
1354 the ideal slope, how close the result is to the ideal stems, etc.). We
1355 take the best scoring combination.
1358 "concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy direction damping flag-width-function neutral-direction positions thickness");