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));
71 Beam::get_interbeam (Grob *me)
73 SCM func = me->get_grob_property ("space-function");
74 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
75 return gh_scm2double (s);
79 Beam::get_multiplicity (Grob *me)
82 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
84 Grob *sc = unsmob_grob (ly_car (s));
86 if (Stem::has_interface (sc))
87 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
92 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
94 Beam::space_function (SCM smob, SCM multiplicity)
96 Grob *me = unsmob_grob (smob);
98 Real staff_space = Staff_symbol_referencer::staff_space (me);
99 Real line = me->paper_l ()->get_var ("linethickness");
100 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
103 Real interbeam = gh_scm2int (multiplicity) < 4
104 ? (2*staff_space + line - thickness) / 2.0
105 : (3*staff_space + line - thickness) / 3.0;
107 return gh_double2scm (interbeam);
111 /* After pre-processing all directions should be set.
112 Several post-processing routines (stem, slur, script) need stem/beam
114 Currenly, this means that beam has set all stem's directions.
115 [Alternatively, stems could set its own directions, according to
116 their beam, during 'final-pre-processing'.] */
117 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
119 Beam::before_line_breaking (SCM smob)
121 Grob *me = unsmob_grob (smob);
123 /* Beams with less than 2 two stems don't make much sense, but could happen
128 For a beam that only has one stem, we try to do some disappearance magic:
129 we revert the flag, and move on to The Eternal Engraving Fields. */
131 int count = visible_stem_count (me);
134 me->warning (_ ("beam has less than two visible stems"));
136 SCM stems = me->get_grob_property ("stems");
137 if (scm_ilength (stems) == 1)
139 me->warning (_ ("Beam has less than two stems. Removing beam."));
141 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
144 return SCM_UNSPECIFIED;
146 else if (scm_ilength (stems) == 0)
149 return SCM_UNSPECIFIED;
154 if (!Directional_element_interface::get (me))
155 Directional_element_interface::set (me, get_default_dir (me));
157 consider_auto_knees (me);
158 set_stem_directions (me);
159 set_stem_shorten (me);
165 Beam::get_default_dir (Grob *me)
167 Drul_array<int> total;
168 total[UP] = total[DOWN] = 0;
169 Drul_array<int> count;
170 count[UP] = count[DOWN] = 0;
173 Link_array<Item> stems=
174 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
176 for (int i=0; i <stems.size (); i++)
179 Direction sd = Directional_element_interface::get (s);
181 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
182 int current = sd ? (1 + d * sd)/2 : center_distance;
189 } while (flip (&d) != DOWN);
191 SCM func = me->get_grob_property ("dir-function");
192 SCM s = gh_call2 (func,
193 gh_cons (gh_int2scm (count[UP]),
194 gh_int2scm (count[DOWN])),
195 gh_cons (gh_int2scm (total[UP]),
196 gh_int2scm (total[DOWN])));
198 if (gh_number_p (s) && gh_scm2int (s))
201 /* If dir is not determined: get default */
202 return to_dir (me->get_grob_property ("neutral-direction"));
206 /* Set all stems with non-forced direction to beam direction.
207 Urg: non-forced should become `without/with unforced' direction,
208 once stem gets cleaned-up. */
210 Beam::set_stem_directions (Grob *me)
212 Link_array<Item> stems
213 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
214 Direction d = Directional_element_interface::get (me);
216 for (int i=0; i <stems.size (); i++)
219 SCM force = s->remove_grob_property ("dir-forced");
220 if (!gh_boolean_p (force) || !gh_scm2bool (force))
221 Directional_element_interface::set (s, d);
225 /* Simplistic auto-knees; only consider vertical gap between two
228 `Forced' stem directions are ignored. If you don't want auto-knees,
229 don't set, or unset auto-knee-gap. */
231 Beam::consider_auto_knees (Grob *me)
233 SCM scm = me->get_grob_property ("auto-knee-gap");
235 if (gh_number_p (scm))
239 Real staff_space = Staff_symbol_referencer::staff_space (me);
240 Real gap = gh_scm2double (scm) / staff_space;
242 Direction d = Directional_element_interface::get (me);
243 Link_array<Item> stems=
244 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
246 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
247 for (int i=1; i < stems.size (); i++)
248 if (!Stem::invisible_b (stems[i]))
249 common = common->common_refpoint (stems[i], Y_AXIS);
252 for (int i=1; i < stems.size (); i++)
254 if (!Stem::invisible_b (stems[i-1]))
256 if (Stem::invisible_b (stems[l]))
258 if (Stem::invisible_b (stems[i]))
261 Real left = Stem::extremal_heads (stems[l])[d]
262 ->relative_coordinate (common, Y_AXIS);
263 Real right = Stem::extremal_heads (stems[i])[-d]
264 ->relative_coordinate (common, Y_AXIS);
266 Real dy = right - left;
270 knee_y = (right + left) / 2;
278 for (int i=0; i < stems.size (); i++)
280 if (Stem::invisible_b (stems[i]))
283 Real y = Stem::extremal_heads (stems[i])[d]
284 ->relative_coordinate (common, Y_AXIS);
286 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
287 s->set_grob_property ("dir-forced", SCM_BOOL_T);
293 /* Set stem's shorten property if unset.
296 take some y-position (chord/beam/nearest?) into account
297 scmify forced-fraction
301 why is shorten stored in beam, and not directly in stem?
305 Beam::set_stem_shorten (Grob *m)
307 Spanner*me = dynamic_cast<Spanner*> (m);
309 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
311 int multiplicity = get_multiplicity (me);
313 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
314 if (shorten == SCM_EOL)
317 int sz = scm_ilength (shorten);
319 Real staff_space = Staff_symbol_referencer::staff_space (me);
320 SCM shorten_elt = scm_list_ref (shorten,
321 gh_int2scm (multiplicity <? (sz - 1)));
322 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
324 /* your similar cute comment here */
325 shorten_f *= forced_fraction;
327 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
330 /* Call list of y-dy-callbacks, that handle setting of
331 grob-properties y, dy.
333 User may set grob-properties: y-position-hs and height-hs
334 (to be fixed) that override the calculated y and dy.
336 Because y and dy cannot be calculated and quanted separately, we
337 always calculate both, then check for user override. */
338 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
340 Beam::after_line_breaking (SCM smob)
342 Grob *me = unsmob_grob (smob);
344 /* Copy to mutable list. */
345 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
346 me->set_grob_property ("positions", s);
348 if (ly_car (s) != SCM_BOOL_F)
349 return SCM_UNSPECIFIED;
351 // one wonders if such genericity is necessary --hwn.
352 SCM callbacks = me->get_grob_property ("position-callbacks");
353 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
354 gh_call1 (ly_car (i), smob);
356 set_stem_lengths (me);
357 return SCM_UNSPECIFIED;
371 - Make all demerits customisable
373 - One sensible check per demerit (what's this --hwn)
375 - Add demerits for quants per se, as to forbid a specific quant
379 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
381 Beam::quanting (SCM smob)
383 Grob *me = unsmob_grob (smob);
385 SCM s = me->get_grob_property ("positions");
386 Real yl = gh_scm2double (gh_car (s));
387 Real yr = gh_scm2double (gh_cdr (s));
389 Real ss = Staff_symbol_referencer::staff_space (me);
390 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
391 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
394 SCM sdy = me->get_grob_property ("least-squares-dy");
395 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
398 Real sit = (thickness - slt) / 2;
400 Real hang = 1.0 - (thickness - slt) / 2;
401 Real quants [] = {straddle, sit, inter, hang };
403 int num_quants = int (sizeof (quants)/sizeof (Real));
408 going to REGION_SIZE == 2, yields another 0.6 second with
412 (result indexes between 70 and 575) ? --hwn.
416 const int REGION_SIZE = 3;
417 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
418 for (int j = 0; j < num_quants; j++)
420 quantsl.push (i + quants[j] + int (yl));
421 quantsr.push (i + quants[j] + int (yr));
424 Array<Quant_score> qscores;
426 for (int l =0; l < quantsl.size (); l++)
427 for (int r =0; r < quantsr.size (); r++)
439 This is a longish function, but we don't separate this out into
440 neat modular separate subfunctions, as the subfunctions would be
441 called for many values of YL, YR. By precomputing various
442 parameters outside of the loop, we can save a lot of time.
445 for (int i = qscores.size (); i--;)
446 if (qscores[i].demerits < 100)
449 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
453 Real rad = Staff_symbol_referencer::staff_radius (me);
454 int multiplicity = get_multiplicity (me);
455 Real interbeam = multiplicity < 4
456 ? (2*ss + slt - thickness) / 2.0
457 : (3*ss + slt - thickness) / 3.0;
459 for (int i = qscores.size (); i--;)
460 if (qscores[i].demerits < 100)
463 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
464 rad, slt, thickness, interbeam,
470 Do stem lengths. These depend on YL and YR linearly, so we can
471 precompute for every stem 2 factors.
473 Link_array<Grob> stems=
474 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
475 Array<Stem_info> stem_infos;
476 Array<Real> lbase_lengths;
477 Array<Real> rbase_lengths;
479 Array<int> directions;
480 for (int i= 0; i < stems.size(); i++)
483 stem_infos.push( Stem::calc_stem_info (s));
485 Real b = calc_stem_y (me, s, Interval (1,0));
486 lbase_lengths.push (b);
488 b = calc_stem_y (me, s, Interval (0,1));
489 rbase_lengths.push (b);
490 directions.push( Directional_element_interface::get( s));
493 for (int i = qscores.size (); i--;)
494 if (qscores[i].demerits < 100)
497 += score_stem_lengths (stems, stem_infos,
498 lbase_lengths, rbase_lengths,
500 me, qscores[i].yl, qscores[i].yr);
506 for (int i = qscores.size (); i--;)
508 if (qscores[i].demerits < best)
510 best = qscores [i].demerits ;
516 me->set_grob_property ("positions",
517 gh_cons (gh_double2scm (qscores[best_idx].yl),
518 gh_double2scm (qscores[best_idx].yr))
524 me->set_grob_property ("quant-score",
525 gh_double2scm (qscores[best_idx].demerits));
526 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
529 return SCM_UNSPECIFIED;
533 Beam::score_stem_lengths (Link_array<Grob>stems,
534 Array<Stem_info> stem_infos,
535 Array<Real> left_factor,
536 Array<Real> right_factor,
537 Array<int> directions,
538 Grob*me, Real yl, Real yr)
540 Real demerit_score = 0.0 ;
542 for (int i=0; i < stems.size (); i++)
545 if (Stem::invisible_b (s))
549 yl * left_factor[i] + right_factor[i]* yr;
551 Stem_info info = stem_infos[i];
552 Direction d = Direction (directions[i]);
554 demerit_score += 500 * ( 0 >? (info.min_y - d * current_y));
555 demerit_score += 500 * ( 0 >? (d * current_y - info.max_y));
557 demerit_score += 5 * shrink_extra_weight (d * current_y - info.ideal_y);
560 demerit_score *= 2.0 / stems.size ();
562 return demerit_score;
566 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
567 Real dy_mus, Real dy_damp)
572 if (sign (dy_damp) != sign (dy))
577 dem += 400* (0 >? (fabs (dy) - fabs (dy_mus)));
580 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* 10;
587 return x - floor (x);
591 Beam::score_forbidden_quants (Grob*me,
595 Real thickness, Real interbeam,
601 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
603 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
606 // todo: use multiplicity of outer stems.
607 if (multiplicity >= 2)
611 Real sit = (thickness - slt) / 2;
613 Real hang = 1.0 - (thickness - slt) / 2;
615 Direction dir = Directional_element_interface::get (me);
616 if (fabs (yl - dir * interbeam) < rad
617 && fabs (my_modf (yl) - inter) < 1e-3)
619 if (fabs (yr - dir * interbeam) < rad
620 && fabs (my_modf (yr) - inter) < 1e-3)
626 Can't we simply compute the distance between the nearest
627 staffline and the secondary beam? That would get rid of the
628 silly case analysis here (which is probably not when we have
629 different beam-thicknesses.)
634 // hmm, without Interval/Drul_array, you get ~ 4x same code...
635 if (fabs (yl - dir * interbeam) < rad + inter)
637 if (dir == UP && dy <= eps
638 && fabs (my_modf (yl) - sit) < eps)
641 if (dir == DOWN && dy >= eps
642 && fabs (my_modf (yl) - hang) < eps)
646 if (fabs (yr - dir * interbeam) < rad + inter)
648 if (dir == UP && dy >= eps
649 && fabs (my_modf (yr) - sit) < eps)
652 if (dir == DOWN && dy <= eps
653 && fabs (my_modf (yr) - hang) < eps)
657 if (multiplicity >= 3)
659 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
661 if (dir == UP && dy <= eps
662 && fabs (my_modf (yl) - straddle) < eps)
665 if (dir == DOWN && dy >= eps
666 && fabs (my_modf (yl) - straddle) < eps)
670 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
672 if (dir == UP && dy >= eps
673 && fabs (my_modf (yr) - straddle) < eps)
676 if (dir == DOWN && dy <= eps
677 && fabs (my_modf (yr) - straddle) < eps)
688 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
690 Beam::least_squares (SCM smob)
692 Grob *me = unsmob_grob (smob);
694 int count = visible_stem_count (me);
699 me->set_grob_property ("positions", ly_interval2scm (pos));
700 return SCM_UNSPECIFIED;
703 Direction dir = Directional_element_interface::get (me);
705 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
706 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
710 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
711 Stem::chord_start_f (last_visible_stem (me)));
715 TODO : use scoring for this.
717 complicated, because we take stem-info.ideal for determining
721 /* Make simple beam on middle line have small tilt */
722 if (!ideal[LEFT] && chord.delta () && count == 2)
724 Direction d = (Direction) (sign (chord.delta ()) * dir);
725 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
738 Array<Offset> ideals;
740 // ugh -> use commonx
741 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
742 Link_array<Item> stems=
743 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
745 for (int i=0; i < stems.size (); i++)
748 if (Stem::invisible_b (s))
750 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
751 Stem::calc_stem_info (s).ideal_y));
755 minimise_least_squares (&dydx, &y, ideals);
757 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
759 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
761 pos = Interval (y*dir, (y+dy) * dir);
764 me->set_grob_property ("positions", ly_interval2scm (pos));
765 return SCM_UNSPECIFIED;
768 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
770 Beam::check_concave (SCM smob)
772 Grob *me = unsmob_grob (smob);
774 Link_array<Item> stems =
775 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
777 for (int i = 0; i < stems.size ();)
779 if (Stem::invisible_b (stems[i]))
785 if (stems.size () < 3)
786 return SCM_UNSPECIFIED;
788 Direction dir = Directional_element_interface::get (me);
789 /* Concaveness #1: If distance of an inner notehead to line between
790 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
791 beam is concave (Heinz Stolba). */
792 bool concaveness1 = false;
793 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
796 Real dy = Stem::chord_start_f (stems.top ())
797 - Stem::chord_start_f (stems[0]);
798 Real slope = dy / (stems.size () - 1);
800 Real y0 = Stem::chord_start_f (stems[0]);
801 for (int i = 1; i < stems.size () - 1; i++)
803 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
813 /* Concaveness #2: Sum distances of inner noteheads that fall
814 outside the interval of the two outer noteheads */
815 Real concaveness2 = 0;
816 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
817 if (!concaveness1 && r2 > 0)
820 Interval iv (Stem::chord_start_f (stems[0]),
821 Stem::chord_start_f (stems.top ()));
823 if (iv[MAX] < iv[MIN])
826 for (int i = 1; i < stems.size () - 1; i++)
829 Real f = Stem::chord_start_f (stems[i]);
830 if ((c = f - iv[MAX]) > 0)
832 else if ((c = f - iv[MIN]) < 0)
838 concaveness2 = concave / (stems.size () - 2);
839 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
840 to behave as baerenreiter. */
841 concaveness2 /= (stems.size () - 2);
844 /* TODO: some sort of damping iso -> plain horizontal */
845 if (concaveness1 || concaveness2 > r2)
847 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
848 Real r = pos.linear_combination (0);
849 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
850 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
853 return SCM_UNSPECIFIED;
856 /* This neat trick is by Werner Lemberg,
857 damped = tanh (slope)
858 corresponds with some tables in [Wanske] CHECKME */
859 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
861 Beam::slope_damping (SCM smob)
863 Grob *me = unsmob_grob (smob);
865 if (visible_stem_count (me) <= 1)
866 return SCM_UNSPECIFIED;
868 SCM s = me->get_grob_property ("damping");
869 int damping = gh_scm2int (s);
873 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
874 Real dy = pos.delta ();
876 // ugh -> use commonx
877 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
878 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
879 Real dydx = dy && dx ? dy/dx : 0;
880 dydx = 0.6 * tanh (dydx) / damping;
882 Real damped_dy = dydx * dx;
883 pos[LEFT] += (dy - damped_dy) / 2;
884 pos[RIGHT] -= (dy - damped_dy) / 2;
886 me->set_grob_property ("positions", ly_interval2scm (pos));
888 return SCM_UNSPECIFIED;
892 Calculate the Y position of the stem-end, given the Y-left, Y-right
893 in POS, and for stem S.
896 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
898 int beam_multiplicity = get_multiplicity (me);
899 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
901 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
902 Real interbeam = get_interbeam (me);
904 // ugh -> use commonx
905 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
906 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
907 Real dy = pos.delta ();
908 Real stem_y = (dy && dx
909 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
914 Direction dir = Directional_element_interface::get (me);
915 Direction sdir = Directional_element_interface::get (s);
920 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
922 // huh, why not for first visible?
924 Grob *last_visible = last_visible_stem (me);
927 if ( Staff_symbol_referencer::staff_symbol_l (s)
928 != Staff_symbol_referencer::staff_symbol_l (last_visible))
929 stem_y += Directional_element_interface::get (me)
930 * (beam_multiplicity - stem_multiplicity) * interbeam;
933 programming_error ("No last visible stem");
940 Hmm. At this time, beam position and slope are determined. Maybe,
941 stem directions and length should set to relative to the chord's
942 position of the beam. */
944 Beam::set_stem_lengths (Grob *me)
946 Link_array<Item> stems=
947 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
949 if (stems.size () <= 1)
952 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
953 for (int i=1; i < stems.size (); i++)
954 if (!Stem::invisible_b (stems[i]))
955 common = common->common_refpoint (stems[i], Y_AXIS);
957 Direction dir = Directional_element_interface::get (me);
958 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
959 Real staff_space = Staff_symbol_referencer::staff_space (me);
960 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
961 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
962 for (int i=0; i < stems.size (); i++)
965 if (Stem::invisible_b (s))
968 Real stem_y = calc_stem_y (me, s, pos);
970 // doesn't play well with dvips
972 if (Stem::get_direction (s) == dir)
973 stem_y += Stem::get_direction (s) * thick / 2;
975 /* caution: stem measures in staff-positions */
976 Real id = me->relative_coordinate (common, Y_AXIS)
977 - stems[i]->relative_coordinate (common, Y_AXIS);
978 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
983 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
985 Link_array<Grob> stems=
986 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
989 for (int i=0; i < stems.size (); i++)
993 /* Don't overwrite user override (?) */
994 if (Stem::beam_count (stems[i], d) == -1
995 /* Don't set beaming for outside of outer stems */
996 && ! (d == LEFT && i == 0)
997 && ! (d == RIGHT && i == stems.size () -1))
999 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1000 Stem::set_beaming (stems[i], b, d);
1003 while (flip (&d) != LEFT);
1010 beams to go with one stem.
1015 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1017 // ugh -> use commonx
1019 && ! (next->relative_coordinate (0, X_AXIS)
1020 > here->relative_coordinate (0, X_AXIS)))
1022 && ! (prev->relative_coordinate (0, X_AXIS)
1023 < here->relative_coordinate (0, X_AXIS))))
1024 programming_error ("Beams are not left-to-right");
1026 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1027 Real bdy = get_interbeam (me);
1030 Molecule rightbeams;
1033 if (!Stem::first_head (here))
1036 int t = Stem::type_i (here);
1038 SCM proc = me->get_grob_property ("flag-width-function");
1039 SCM result = gh_call1 (proc, gh_int2scm (t));
1040 nw_f = gh_scm2double (result);
1044 Direction dir = Directional_element_interface::get (me);
1046 /* [Tremolo] beams on whole notes may not have direction set? */
1048 dir = Directional_element_interface::get (here);
1051 /* half beams extending to the left. */
1054 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1055 - Stem::beam_count (prev, RIGHT);
1056 int lwholebeams= Stem::beam_count (here, LEFT)
1057 <? Stem::beam_count (prev, RIGHT);
1059 /* Half beam should be one note-width,
1060 but let's make sure two half-beams never touch */
1062 // FIXME: TODO (check) stem width / sloped beams
1063 Real w = here->relative_coordinate (0, X_AXIS)
1064 - prev->relative_coordinate (0, X_AXIS);
1065 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1067 * me->paper_l ()->get_var ("linethickness");
1071 if (lhalfs) // generates warnings if not
1072 a = Lookup::beam (dydx, w + stem_w, thick);
1073 a.translate (Offset (-w, -w * dydx));
1074 a.translate_axis (-stem_w/2, X_AXIS);
1075 for (int j = 0; j < lhalfs; j++)
1078 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1079 leftbeams.add_molecule (b);
1085 int rhalfs = Stem::beam_count (here, RIGHT)
1086 - Stem::beam_count (next, LEFT);
1087 int rwholebeams= Stem::beam_count (here, RIGHT)
1088 <? Stem::beam_count (next, LEFT);
1090 Real w = next->relative_coordinate (0, X_AXIS)
1091 - here->relative_coordinate (0, X_AXIS);
1093 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1095 * me->paper_l ()->get_var ("linethickness");
1097 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1098 a.translate_axis (- stem_w/2, X_AXIS);
1102 SCM gap = me->get_grob_property ("gap");
1103 if (gh_number_p (gap))
1105 int gap_i = gh_scm2int ((gap));
1106 int nogap = rwholebeams - gap_i;
1108 for (; j < nogap; j++)
1111 b.translate_axis (-dir * bdy * j, Y_AXIS);
1112 rightbeams.add_molecule (b);
1114 if (Stem::invisible_b (here))
1119 a = Lookup::beam (dydx, w + stem_w, thick);
1122 for (; j < rwholebeams; j++)
1126 if (Stem::invisible_b (here))
1127 // ugh, see chord-tremolo.ly
1128 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1131 b.translate (Offset (tx, -dir * bdy * j));
1132 rightbeams.add_molecule (b);
1137 a = Lookup::beam (dydx, w, thick);
1139 for (; j < rwholebeams + rhalfs; j++)
1142 b.translate_axis (- dir * bdy * j, Y_AXIS);
1143 rightbeams.add_molecule (b);
1147 leftbeams.add_molecule (rightbeams);
1149 /* Does beam quanting think of the asymetry of beams?
1150 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1155 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1157 Beam::brew_molecule (SCM smob)
1159 Grob *me =unsmob_grob (smob);
1162 if (!gh_pair_p (me->get_grob_property ("stems")))
1165 Link_array<Item>stems =
1166 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1167 if (visible_stem_count (me))
1169 // ugh -> use commonx
1170 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1171 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1175 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1176 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1179 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1180 Real dy = pos.delta ();
1181 Real dydx = dy && dx ? dy/dx : 0;
1183 for (int i=0; i < stems.size (); i++)
1185 Item *item = stems[i];
1186 Item *prev = (i > 0)? stems[i-1] : 0;
1187 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1189 Molecule sb = stem_beams (me, item, next, prev, dydx);
1190 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1191 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1192 mol.add_molecule (sb);
1195 mol.translate_axis (x0
1196 - dynamic_cast<Spanner*> (me)
1197 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1203 This code prints the demerits for each beam. Perhaps this
1204 should be switchable for those who want to twiddle with the
1210 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1213 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1216 SCM properties = Font_interface::font_alist_chain (me);
1218 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1219 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1222 return mol.smobbed_copy ();
1226 Beam::forced_stem_count (Grob *me)
1228 Link_array<Item>stems =
1229 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1231 for (int i=0; i < stems.size (); i++)
1235 if (Stem::invisible_b (s))
1238 if (((int)Stem::chord_start_f (s))
1239 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1249 Beam::visible_stem_count (Grob *me)
1251 Link_array<Item>stems =
1252 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1254 for (int i = stems.size (); i--;)
1256 if (!Stem::invisible_b (stems[i]))
1263 Beam::first_visible_stem (Grob *me)
1265 Link_array<Item>stems =
1266 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1268 for (int i = 0; i < stems.size (); i++)
1270 if (!Stem::invisible_b (stems[i]))
1277 Beam::last_visible_stem (Grob *me)
1279 Link_array<Item>stems =
1280 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1281 for (int i = stems.size (); i--;)
1283 if (!Stem::invisible_b (stems[i]))
1293 handle rest under beam (do_post: beams are calculated now)
1294 what about combination of collisions and rest under beam.
1298 rest -> stem -> beam -> interpolate_y_position ()
1300 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1302 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1304 Grob *rest = unsmob_grob (element_smob);
1305 Axis a = (Axis) gh_scm2int (axis);
1307 assert (a == Y_AXIS);
1309 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1312 return gh_double2scm (0.0);
1313 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1315 || !Beam::has_interface (beam)
1316 || !Beam::visible_stem_count (beam))
1317 return gh_double2scm (0.0);
1319 // make callback for rest from this.
1320 // todo: make sure this calced already.
1322 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1323 Interval pos (0, 0);
1324 SCM s = beam->get_grob_property ("positions");
1325 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1326 pos = ly_scm2interval (s);
1328 Real dy = pos.delta ();
1329 // ugh -> use commonx
1330 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1331 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1332 Real dydx = dy && dx ? dy/dx : 0;
1334 Direction d = Stem::get_direction (stem);
1335 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1337 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1340 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1343 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1345 minimum_dist + -d * (beamy - rest_dim) >? 0;
1347 int stafflines = Staff_symbol_referencer::line_count (rest);
1349 // move discretely by half spaces.
1350 int discrete_dist = int (ceil (dist));
1352 // move by whole spaces inside the staff.
1353 if (discrete_dist < stafflines+1)
1354 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1356 return gh_double2scm (-d * discrete_dist);
1362 ADD_INTERFACE (Beam, "beam-interface",
1365 #'thickness= weight of beams, in staffspace
1368 We take the least squares line through the ideal-length stems, and
1369 then damp that using
1371 damped = tanh (slope)
1373 this gives an unquantized left and right position for the beam end.
1374 Then we take all combinations of quantings near these left and right
1375 positions, and give them a score (according to how close they are to
1376 the ideal slope, how close the result is to the ideal stems, etc.). We
1377 take the best scoring combination.
1380 "position-callbacks concaveness-gap 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 space-function thickness");