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).
30 #include <math.h> // tanh.
32 #include "molecule.hh"
33 #include "directional-element-interface.hh"
37 #include "least-squares.hh"
39 #include "paper-def.hh"
41 #include "group-interface.hh"
42 #include "staff-symbol-referencer.hh"
46 #include "text-item.hh" // debug output.
47 #include "font-interface.hh" // debug output.
50 #define DEBUG_QUANTING 0
52 const int INTER_QUANT_PENALTY = 1000;
53 const int SECONDARY_BEAM_DEMERIT = 15;
54 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
55 const int STEM_LENGTH_LIMIT_PENALTY = 500;
56 const int DAMPING_DIRECTIION_PENALTY = 800;
57 const int MUSICAL_DIRECTION_FACTOR = 400;
58 const int IDEAL_SLOPE_FACTOR = 10;
62 shrink_extra_weight (Real x)
64 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
68 Beam::add_stem (Grob *me, Grob *s)
70 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
72 s->add_dependency (me);
74 assert (!Stem::beam_l (s));
75 s->set_grob_property ("beam", me->self_scm ());
77 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
81 Beam::get_interbeam (Grob *me)
83 SCM func = me->get_grob_property ("space-function");
84 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
85 return gh_scm2double (s);
89 Beam::get_multiplicity (Grob *me)
92 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
94 Grob *sc = unsmob_grob (ly_car (s));
96 if (Stem::has_interface (sc))
97 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
102 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
104 Beam::space_function (SCM smob, SCM multiplicity)
106 Grob *me = unsmob_grob (smob);
108 Real staff_space = Staff_symbol_referencer::staff_space (me);
109 Real line = me->paper_l ()->get_var ("linethickness");
110 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
113 Real interbeam = gh_scm2int (multiplicity) < 4
114 ? (2*staff_space + line - thickness) / 2.0
115 : (3*staff_space + line - thickness) / 3.0;
117 return gh_double2scm (interbeam);
121 /* After pre-processing all directions should be set.
122 Several post-processing routines (stem, slur, script) need stem/beam
124 Currenly, this means that beam has set all stem's directions.
125 [Alternatively, stems could set its own directions, according to
126 their beam, during 'final-pre-processing'.] */
127 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
129 Beam::before_line_breaking (SCM smob)
131 Grob *me = unsmob_grob (smob);
133 /* Beams with less than 2 two stems don't make much sense, but could happen
138 For a beam that only has one stem, we try to do some disappearance magic:
139 we revert the flag, and move on to The Eternal Engraving Fields. */
141 int count = visible_stem_count (me);
144 me->warning (_ ("beam has less than two visible stems"));
146 SCM stems = me->get_grob_property ("stems");
147 if (scm_ilength (stems) == 1)
149 me->warning (_ ("Beam has less than two stems. Removing beam."));
151 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
154 return SCM_UNSPECIFIED;
156 else if (scm_ilength (stems) == 0)
159 return SCM_UNSPECIFIED;
164 if (!Directional_element_interface::get (me))
165 Directional_element_interface::set (me, get_default_dir (me));
167 consider_auto_knees (me);
168 set_stem_directions (me);
169 set_stem_shorten (me);
175 Beam::get_default_dir (Grob *me)
177 Drul_array<int> total;
178 total[UP] = total[DOWN] = 0;
179 Drul_array<int> count;
180 count[UP] = count[DOWN] = 0;
183 Link_array<Item> stems=
184 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
186 for (int i=0; i <stems.size (); i++)
189 Direction sd = Directional_element_interface::get (s);
191 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
192 int current = sd ? (1 + d * sd)/2 : center_distance;
199 } while (flip (&d) != DOWN);
201 SCM func = me->get_grob_property ("dir-function");
202 SCM s = gh_call2 (func,
203 gh_cons (gh_int2scm (count[UP]),
204 gh_int2scm (count[DOWN])),
205 gh_cons (gh_int2scm (total[UP]),
206 gh_int2scm (total[DOWN])));
208 if (gh_number_p (s) && gh_scm2int (s))
211 /* If dir is not determined: get default */
212 return to_dir (me->get_grob_property ("neutral-direction"));
216 /* Set all stems with non-forced direction to beam direction.
217 Urg: non-forced should become `without/with unforced' direction,
218 once stem gets cleaned-up. */
220 Beam::set_stem_directions (Grob *me)
222 Link_array<Item> stems
223 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
224 Direction d = Directional_element_interface::get (me);
226 for (int i=0; i <stems.size (); i++)
229 SCM force = s->remove_grob_property ("dir-forced");
230 if (!gh_boolean_p (force) || !gh_scm2bool (force))
231 Directional_element_interface::set (s, d);
235 /* Simplistic auto-knees; only consider vertical gap between two
238 `Forced' stem directions are ignored. If you don't want auto-knees,
239 don't set, or unset auto-knee-gap. */
241 Beam::consider_auto_knees (Grob *me)
243 SCM scm = me->get_grob_property ("auto-knee-gap");
245 if (gh_number_p (scm))
249 Real staff_space = Staff_symbol_referencer::staff_space (me);
250 Real gap = gh_scm2double (scm) / staff_space;
252 Direction d = Directional_element_interface::get (me);
253 Link_array<Item> stems=
254 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
256 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
257 for (int i=1; i < stems.size (); i++)
258 if (!Stem::invisible_b (stems[i]))
259 common = common->common_refpoint (stems[i], Y_AXIS);
262 for (int i=1; i < stems.size (); i++)
264 if (!Stem::invisible_b (stems[i-1]))
266 if (Stem::invisible_b (stems[l]))
268 if (Stem::invisible_b (stems[i]))
271 Real left = Stem::extremal_heads (stems[l])[d]
272 ->relative_coordinate (common, Y_AXIS);
273 Real right = Stem::extremal_heads (stems[i])[-d]
274 ->relative_coordinate (common, Y_AXIS);
276 Real dy = right - left;
280 knee_y = (right + left) / 2;
288 for (int i=0; i < stems.size (); i++)
290 if (Stem::invisible_b (stems[i]))
293 Real y = Stem::extremal_heads (stems[i])[d]
294 ->relative_coordinate (common, Y_AXIS);
296 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
297 s->set_grob_property ("dir-forced", SCM_BOOL_T);
303 /* Set stem's shorten property if unset.
306 take some y-position (chord/beam/nearest?) into account
307 scmify forced-fraction
311 why is shorten stored in beam, and not directly in stem?
315 Beam::set_stem_shorten (Grob *m)
317 Spanner*me = dynamic_cast<Spanner*> (m);
319 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
321 int multiplicity = get_multiplicity (me);
323 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
324 if (shorten == SCM_EOL)
327 int sz = scm_ilength (shorten);
329 Real staff_space = Staff_symbol_referencer::staff_space (me);
330 SCM shorten_elt = scm_list_ref (shorten,
331 gh_int2scm (multiplicity <? (sz - 1)));
332 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
334 /* your similar cute comment here */
335 shorten_f *= forced_fraction;
337 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
340 /* Call list of y-dy-callbacks, that handle setting of
341 grob-properties y, dy.
343 User may set grob-properties: y-position-hs and height-hs
344 (to be fixed) that override the calculated y and dy.
346 Because y and dy cannot be calculated and quanted separately, we
347 always calculate both, then check for user override. */
348 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
350 Beam::after_line_breaking (SCM smob)
352 Grob *me = unsmob_grob (smob);
354 /* Copy to mutable list. */
355 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
356 me->set_grob_property ("positions", s);
358 if (ly_car (s) != SCM_BOOL_F)
359 return SCM_UNSPECIFIED;
361 // one wonders if such genericity is necessary --hwn.
362 SCM callbacks = me->get_grob_property ("position-callbacks");
363 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
364 gh_call1 (ly_car (i), smob);
366 set_stem_lengths (me);
367 return SCM_UNSPECIFIED;
381 - Make all demerits customisable
383 - One sensible check per demerit (what's this --hwn)
385 - Add demerits for quants per se, as to forbid a specific quant
389 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
391 Beam::quanting (SCM smob)
393 Grob *me = unsmob_grob (smob);
395 SCM s = me->get_grob_property ("positions");
396 Real yl = gh_scm2double (gh_car (s));
397 Real yr = gh_scm2double (gh_cdr (s));
399 Real ss = Staff_symbol_referencer::staff_space (me);
400 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
401 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
404 SCM sdy = me->get_grob_property ("least-squares-dy");
405 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
408 Real sit = (thickness - slt) / 2;
410 Real hang = 1.0 - (thickness - slt) / 2;
411 Real quants [] = {straddle, sit, inter, hang };
413 int num_quants = int (sizeof (quants)/sizeof (Real));
418 going to REGION_SIZE == 2, yields another 0.6 second with
422 (result indexes between 70 and 575) ? --hwn.
426 const int REGION_SIZE = 3;
427 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
428 for (int j = 0; j < num_quants; j++)
430 quantsl.push (i + quants[j] + int (yl));
431 quantsr.push (i + quants[j] + int (yr));
434 Array<Quant_score> qscores;
436 for (int l =0; l < quantsl.size (); l++)
437 for (int r =0; r < quantsr.size (); r++)
449 This is a longish function, but we don't separate this out into
450 neat modular separate subfunctions, as the subfunctions would be
451 called for many values of YL, YR. By precomputing various
452 parameters outside of the loop, we can save a lot of time.
455 for (int i = qscores.size (); i--;)
456 if (qscores[i].demerits < 100)
459 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
463 Real rad = Staff_symbol_referencer::staff_radius (me);
464 int multiplicity = get_multiplicity (me);
465 Real interbeam = multiplicity < 4
466 ? (2*ss + slt - thickness) / 2.0
467 : (3*ss + slt - thickness) / 3.0;
469 for (int i = qscores.size (); i--;)
470 if (qscores[i].demerits < 100)
473 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
474 rad, slt, thickness, interbeam,
480 Do stem lengths. These depend on YL and YR linearly, so we can
481 precompute for every stem 2 factors.
483 Link_array<Grob> stems=
484 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
485 Array<Stem_info> stem_infos;
486 Array<Real> lbase_lengths;
487 Array<Real> rbase_lengths;
489 Array<int> directions;
490 for (int i= 0; i < stems.size(); i++)
493 stem_infos.push( Stem::calc_stem_info (s));
495 Real b = calc_stem_y (me, s, Interval (1,0));
496 lbase_lengths.push (b);
498 b = calc_stem_y (me, s, Interval (0,1));
499 rbase_lengths.push (b);
500 directions.push( Directional_element_interface::get( s));
503 for (int i = qscores.size (); i--;)
504 if (qscores[i].demerits < 100)
507 += score_stem_lengths (stems, stem_infos,
508 lbase_lengths, rbase_lengths,
510 me, qscores[i].yl, qscores[i].yr);
516 for (int i = qscores.size (); i--;)
518 if (qscores[i].demerits < best)
520 best = qscores [i].demerits ;
526 me->set_grob_property ("positions",
527 gh_cons (gh_double2scm (qscores[best_idx].yl),
528 gh_double2scm (qscores[best_idx].yr))
534 me->set_grob_property ("quant-score",
535 gh_double2scm (qscores[best_idx].demerits));
536 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
539 return SCM_UNSPECIFIED;
543 Beam::score_stem_lengths (Link_array<Grob>stems,
544 Array<Stem_info> stem_infos,
545 Array<Real> left_factor,
546 Array<Real> right_factor,
547 Array<int> directions,
548 Grob*me, Real yl, Real yr)
550 Real demerit_score = 0.0 ;
552 for (int i=0; i < stems.size (); i++)
555 if (Stem::invisible_b (s))
559 yl * left_factor[i] + right_factor[i]* yr;
561 Stem_info info = stem_infos[i];
562 Direction d = Direction (directions[i]);
564 demerit_score += STEM_LENGTH_LIMIT_PENALTY * ( 0 >? (info.min_y - d * current_y));
565 demerit_score += STEM_LENGTH_LIMIT_PENALTY * ( 0 >? (d * current_y - info.max_y));
567 demerit_score += STEM_LENGTH_DEMERIT_FACTOR * shrink_extra_weight (d * current_y - info.ideal_y);
570 demerit_score *= 2.0 / stems.size ();
572 return demerit_score;
576 Beam::score_slopes_dy (Grob *me, Real yl, Real yr,
577 Real dy_mus, Real dy_damp)
582 if (sign (dy_damp) != sign (dy))
584 dem += DAMPING_DIRECTIION_PENALTY;
587 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
590 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
597 return x - floor (x);
601 Beam::score_forbidden_quants (Grob*me,
605 Real thickness, Real interbeam,
611 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
612 dem += INTER_QUANT_PENALTY;
613 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
614 dem += INTER_QUANT_PENALTY;
616 // todo: use multiplicity of outer stems.
617 if (multiplicity >= 2)
621 Real sit = (thickness - slt) / 2;
623 Real hang = 1.0 - (thickness - slt) / 2;
625 Direction dir = Directional_element_interface::get (me);
626 if (fabs (yl - dir * interbeam) < rad
627 && fabs (my_modf (yl) - inter) < 1e-3)
628 dem += SECONDARY_BEAM_DEMERIT;
629 if (fabs (yr - dir * interbeam) < rad
630 && fabs (my_modf (yr) - inter) < 1e-3)
631 dem += SECONDARY_BEAM_DEMERIT;
636 Can't we simply compute the distance between the nearest
637 staffline and the secondary beam? That would get rid of the
638 silly case analysis here (which is probably not when we have
639 different beam-thicknesses.)
645 // hmm, without Interval/Drul_array, you get ~ 4x same code...
646 if (fabs (yl - dir * interbeam) < rad + inter)
648 if (dir == UP && dy <= eps
649 && fabs (my_modf (yl) - sit) < eps)
650 dem += SECONDARY_BEAM_DEMERIT;
652 if (dir == DOWN && dy >= eps
653 && fabs (my_modf (yl) - hang) < eps)
654 dem += SECONDARY_BEAM_DEMERIT;
657 if (fabs (yr - dir * interbeam) < rad + inter)
659 if (dir == UP && dy >= eps
660 && fabs (my_modf (yr) - sit) < eps)
661 dem += SECONDARY_BEAM_DEMERIT;
663 if (dir == DOWN && dy <= eps
664 && fabs (my_modf (yr) - hang) < eps)
665 dem += SECONDARY_BEAM_DEMERIT;
668 if (multiplicity >= 3)
670 if (fabs (yl - 2 * dir * interbeam) < rad + inter)
672 if (dir == UP && dy <= eps
673 && fabs (my_modf (yl) - straddle) < eps)
674 dem += SECONDARY_BEAM_DEMERIT;
676 if (dir == DOWN && dy >= eps
677 && fabs (my_modf (yl) - straddle) < eps)
678 dem += SECONDARY_BEAM_DEMERIT;
681 if (fabs (yr - 2 * dir * interbeam) < rad + inter)
683 if (dir == UP && dy >= eps
684 && fabs (my_modf (yr) - straddle) < eps)
685 dem += SECONDARY_BEAM_DEMERIT;
687 if (dir == DOWN && dy <= eps
688 && fabs (my_modf (yr) - straddle) < eps)
689 dem += SECONDARY_BEAM_DEMERIT;
699 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
701 Beam::least_squares (SCM smob)
703 Grob *me = unsmob_grob (smob);
705 int count = visible_stem_count (me);
710 me->set_grob_property ("positions", ly_interval2scm (pos));
711 return SCM_UNSPECIFIED;
714 Direction dir = Directional_element_interface::get (me);
716 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y,
717 Stem::calc_stem_info (last_visible_stem (me)).ideal_y);
721 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
722 Stem::chord_start_f (last_visible_stem (me)));
726 TODO : use scoring for this.
728 complicated, because we take stem-info.ideal for determining
732 /* Make simple beam on middle line have small tilt */
733 if (!ideal[LEFT] && chord.delta () && count == 2)
735 Direction d = (Direction) (sign (chord.delta ()) * dir);
736 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
749 Array<Offset> ideals;
751 // ugh -> use commonx
752 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
753 Link_array<Item> stems=
754 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
756 for (int i=0; i < stems.size (); i++)
759 if (Stem::invisible_b (s))
761 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
762 Stem::calc_stem_info (s).ideal_y));
766 minimise_least_squares (&dydx, &y, ideals);
768 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
770 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
772 pos = Interval (y*dir, (y+dy) * dir);
775 me->set_grob_property ("positions", ly_interval2scm (pos));
776 return SCM_UNSPECIFIED;
779 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
781 Beam::check_concave (SCM smob)
783 Grob *me = unsmob_grob (smob);
785 Link_array<Item> stems =
786 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
788 for (int i = 0; i < stems.size ();)
790 if (Stem::invisible_b (stems[i]))
796 if (stems.size () < 3)
797 return SCM_UNSPECIFIED;
799 Direction dir = Directional_element_interface::get (me);
800 /* Concaveness #1: If distance of an inner notehead to line between
801 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
802 beam is concave (Heinz Stolba). */
803 bool concaveness1 = false;
804 Real r1 = gh_scm2double (me->get_grob_property ("concaveness-gap"));
807 Real dy = Stem::chord_start_f (stems.top ())
808 - Stem::chord_start_f (stems[0]);
809 Real slope = dy / (stems.size () - 1);
811 Real y0 = Stem::chord_start_f (stems[0]);
812 for (int i = 1; i < stems.size () - 1; i++)
814 Real c = (Stem::chord_start_f (stems[i]) - y0) - i * slope;
824 /* Concaveness #2: Sum distances of inner noteheads that fall
825 outside the interval of the two outer noteheads */
826 Real concaveness2 = 0;
827 Real r2 = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
828 if (!concaveness1 && r2 > 0)
831 Interval iv (Stem::chord_start_f (stems[0]),
832 Stem::chord_start_f (stems.top ()));
834 if (iv[MAX] < iv[MIN])
837 for (int i = 1; i < stems.size () - 1; i++)
840 Real f = Stem::chord_start_f (stems[i]);
841 if ((c = f - iv[MAX]) > 0)
843 else if ((c = f - iv[MIN]) < 0)
849 concaveness2 = concave / (stems.size () - 2);
850 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
851 to behave as baerenreiter. */
852 concaveness2 /= (stems.size () - 2);
855 /* TODO: some sort of damping iso -> plain horizontal */
856 if (concaveness1 || concaveness2 > r2)
858 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
859 Real r = pos.linear_combination (0);
860 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
861 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
864 return SCM_UNSPECIFIED;
867 /* This neat trick is by Werner Lemberg,
868 damped = tanh (slope)
869 corresponds with some tables in [Wanske] CHECKME */
870 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
872 Beam::slope_damping (SCM smob)
874 Grob *me = unsmob_grob (smob);
876 if (visible_stem_count (me) <= 1)
877 return SCM_UNSPECIFIED;
879 SCM s = me->get_grob_property ("damping");
880 int damping = gh_scm2int (s);
884 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
885 Real dy = pos.delta ();
887 // ugh -> use commonx
888 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
889 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
890 Real dydx = dy && dx ? dy/dx : 0;
891 dydx = 0.6 * tanh (dydx) / damping;
893 Real damped_dy = dydx * dx;
894 pos[LEFT] += (dy - damped_dy) / 2;
895 pos[RIGHT] -= (dy - damped_dy) / 2;
897 me->set_grob_property ("positions", ly_interval2scm (pos));
899 return SCM_UNSPECIFIED;
903 Calculate the Y position of the stem-end, given the Y-left, Y-right
904 in POS, and for stem S.
907 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
909 int beam_multiplicity = get_multiplicity (me);
910 int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
912 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
913 Real interbeam = get_interbeam (me);
915 // ugh -> use commonx
916 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
917 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
918 Real dy = pos.delta ();
919 Real stem_y = (dy && dx
920 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
925 Direction dir = Directional_element_interface::get (me);
926 Direction sdir = Directional_element_interface::get (s);
931 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
933 // huh, why not for first visible?
935 Grob *last_visible = last_visible_stem (me);
938 if ( Staff_symbol_referencer::staff_symbol_l (s)
939 != Staff_symbol_referencer::staff_symbol_l (last_visible))
940 stem_y += Directional_element_interface::get (me)
941 * (beam_multiplicity - stem_multiplicity) * interbeam;
944 programming_error ("No last visible stem");
951 Hmm. At this time, beam position and slope are determined. Maybe,
952 stem directions and length should set to relative to the chord's
953 position of the beam. */
955 Beam::set_stem_lengths (Grob *me)
957 Link_array<Item> stems=
958 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
960 if (stems.size () <= 1)
963 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
964 for (int i=1; i < stems.size (); i++)
965 if (!Stem::invisible_b (stems[i]))
966 common = common->common_refpoint (stems[i], Y_AXIS);
968 Direction dir = Directional_element_interface::get (me);
969 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
970 Real staff_space = Staff_symbol_referencer::staff_space (me);
971 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
972 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
973 for (int i=0; i < stems.size (); i++)
976 if (Stem::invisible_b (s))
979 Real stem_y = calc_stem_y (me, s, pos);
981 // doesn't play well with dvips
983 if (Stem::get_direction (s) == dir)
984 stem_y += Stem::get_direction (s) * thick / 2;
986 /* caution: stem measures in staff-positions */
987 Real id = me->relative_coordinate (common, Y_AXIS)
988 - stems[i]->relative_coordinate (common, Y_AXIS);
989 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
994 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
996 Link_array<Grob> stems=
997 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1000 for (int i=0; i < stems.size (); i++)
1004 /* Don't overwrite user override (?) */
1005 if (Stem::beam_count (stems[i], d) == -1
1006 /* Don't set beaming for outside of outer stems */
1007 && ! (d == LEFT && i == 0)
1008 && ! (d == RIGHT && i == stems.size () -1))
1010 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1011 Stem::set_beaming (stems[i], b, d);
1014 while (flip (&d) != LEFT);
1021 beams to go with one stem.
1026 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1028 // ugh -> use commonx
1030 && ! (next->relative_coordinate (0, X_AXIS)
1031 > here->relative_coordinate (0, X_AXIS)))
1033 && ! (prev->relative_coordinate (0, X_AXIS)
1034 < here->relative_coordinate (0, X_AXIS))))
1035 programming_error ("Beams are not left-to-right");
1037 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1038 Real bdy = get_interbeam (me);
1041 Molecule rightbeams;
1044 if (!Stem::first_head (here))
1047 int t = Stem::type_i (here);
1049 SCM proc = me->get_grob_property ("flag-width-function");
1050 SCM result = gh_call1 (proc, gh_int2scm (t));
1051 nw_f = gh_scm2double (result);
1055 Direction dir = Directional_element_interface::get (me);
1057 /* [Tremolo] beams on whole notes may not have direction set? */
1059 dir = Directional_element_interface::get (here);
1062 /* half beams extending to the left. */
1065 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1066 - Stem::beam_count (prev, RIGHT);
1067 int lwholebeams= Stem::beam_count (here, LEFT)
1068 <? Stem::beam_count (prev, RIGHT);
1070 /* Half beam should be one note-width,
1071 but let's make sure two half-beams never touch */
1073 // FIXME: TODO (check) stem width / sloped beams
1074 Real w = here->relative_coordinate (0, X_AXIS)
1075 - prev->relative_coordinate (0, X_AXIS);
1076 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1078 * me->paper_l ()->get_var ("linethickness");
1082 if (lhalfs) // generates warnings if not
1083 a = Lookup::beam (dydx, w + stem_w, thick);
1084 a.translate (Offset (-w, -w * dydx));
1085 a.translate_axis (-stem_w/2, X_AXIS);
1086 for (int j = 0; j < lhalfs; j++)
1089 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1090 leftbeams.add_molecule (b);
1096 int rhalfs = Stem::beam_count (here, RIGHT)
1097 - Stem::beam_count (next, LEFT);
1098 int rwholebeams= Stem::beam_count (here, RIGHT)
1099 <? Stem::beam_count (next, LEFT);
1101 Real w = next->relative_coordinate (0, X_AXIS)
1102 - here->relative_coordinate (0, X_AXIS);
1104 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1106 * me->paper_l ()->get_var ("linethickness");
1108 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1109 a.translate_axis (- stem_w/2, X_AXIS);
1113 SCM gap = me->get_grob_property ("gap");
1114 if (gh_number_p (gap))
1116 int gap_i = gh_scm2int ((gap));
1117 int nogap = rwholebeams - gap_i;
1119 for (; j < nogap; j++)
1122 b.translate_axis (-dir * bdy * j, Y_AXIS);
1123 rightbeams.add_molecule (b);
1125 if (Stem::invisible_b (here))
1130 a = Lookup::beam (dydx, w + stem_w, thick);
1133 for (; j < rwholebeams; j++)
1137 if (Stem::invisible_b (here))
1138 // ugh, see chord-tremolo.ly
1139 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1142 b.translate (Offset (tx, -dir * bdy * j));
1143 rightbeams.add_molecule (b);
1148 a = Lookup::beam (dydx, w, thick);
1150 for (; j < rwholebeams + rhalfs; j++)
1153 b.translate_axis (- dir * bdy * j, Y_AXIS);
1154 rightbeams.add_molecule (b);
1158 leftbeams.add_molecule (rightbeams);
1160 /* Does beam quanting think of the asymetry of beams?
1161 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1166 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1168 Beam::brew_molecule (SCM smob)
1170 Grob *me =unsmob_grob (smob);
1173 if (!gh_pair_p (me->get_grob_property ("stems")))
1176 Link_array<Item>stems =
1177 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1178 if (visible_stem_count (me))
1180 // ugh -> use commonx
1181 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1182 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1186 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1187 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1190 SCM posns = me->get_grob_property ("positions");
1192 if (!ly_number_pair_p (posns))
1194 programming_error ("No beam posns");
1195 pos = Interval (0,0);
1198 pos= ly_scm2interval (posns);
1199 Real dy = pos.delta ();
1200 Real dydx = dy && dx ? dy/dx : 0;
1202 for (int i=0; i < stems.size (); i++)
1204 Item *item = stems[i];
1205 Item *prev = (i > 0)? stems[i-1] : 0;
1206 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1208 Molecule sb = stem_beams (me, item, next, prev, dydx);
1209 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1210 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1211 mol.add_molecule (sb);
1214 mol.translate_axis (x0
1215 - dynamic_cast<Spanner*> (me)
1216 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1222 This code prints the demerits for each beam. Perhaps this
1223 should be switchable for those who want to twiddle with the
1229 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1232 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1235 SCM properties = Font_interface::font_alist_chain (me);
1237 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C ()), properties);
1238 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1241 return mol.smobbed_copy ();
1245 Beam::forced_stem_count (Grob *me)
1247 Link_array<Item>stems =
1248 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1250 for (int i=0; i < stems.size (); i++)
1254 if (Stem::invisible_b (s))
1257 if (((int)Stem::chord_start_f (s))
1258 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1268 Beam::visible_stem_count (Grob *me)
1270 Link_array<Item>stems =
1271 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1273 for (int i = stems.size (); i--;)
1275 if (!Stem::invisible_b (stems[i]))
1282 Beam::first_visible_stem (Grob *me)
1284 Link_array<Item>stems =
1285 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1287 for (int i = 0; i < stems.size (); i++)
1289 if (!Stem::invisible_b (stems[i]))
1296 Beam::last_visible_stem (Grob *me)
1298 Link_array<Item>stems =
1299 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1300 for (int i = stems.size (); i--;)
1302 if (!Stem::invisible_b (stems[i]))
1312 handle rest under beam (do_post: beams are calculated now)
1313 what about combination of collisions and rest under beam.
1317 rest -> stem -> beam -> interpolate_y_position ()
1319 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1321 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1323 Grob *rest = unsmob_grob (element_smob);
1324 Axis a = (Axis) gh_scm2int (axis);
1326 assert (a == Y_AXIS);
1328 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1331 return gh_double2scm (0.0);
1332 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1334 || !Beam::has_interface (beam)
1335 || !Beam::visible_stem_count (beam))
1336 return gh_double2scm (0.0);
1338 // make callback for rest from this.
1339 // todo: make sure this calced already.
1341 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1342 Interval pos (0, 0);
1343 SCM s = beam->get_grob_property ("positions");
1344 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1345 pos = ly_scm2interval (s);
1347 Real dy = pos.delta ();
1348 // ugh -> use commonx
1349 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1350 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1351 Real dydx = dy && dx ? dy/dx : 0;
1353 Direction d = Stem::get_direction (stem);
1354 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1356 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1359 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1362 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1364 minimum_dist + -d * (beamy - rest_dim) >? 0;
1366 int stafflines = Staff_symbol_referencer::line_count (rest);
1368 // move discretely by half spaces.
1369 int discrete_dist = int (ceil (dist));
1371 // move by whole spaces inside the staff.
1372 if (discrete_dist < stafflines+1)
1373 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1375 return gh_double2scm (-d * discrete_dist);
1381 ADD_INTERFACE (Beam, "beam-interface",
1384 #'thickness= weight of beams, in staffspace
1387 We take the least squares line through the ideal-length stems, and
1388 then damp that using
1390 damped = tanh (slope)
1392 this gives an unquantized left and right position for the beam end.
1393 Then we take all combinations of quantings near these left and right
1394 positions, and give them a score (according to how close they are to
1395 the ideal slope, how close the result is to the ideal stems, etc.). We
1396 take the best scoring combination.
1399 "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");