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>
14 * different left/right quanting: slope, multiplicity
20 * Remove #'direction from beam. A beam has no direction per se.
21 It may only set directions for stems.
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.
50 shrink_extra_weight(Real x)
52 return fabs(x) * ((x < 0) ? 1.5 : 1.0);
57 Beam::add_stem (Grob *me, Grob *s)
59 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
61 s->add_dependency (me);
63 assert (!Stem::beam_l (s));
64 s->set_grob_property ("beam", me->self_scm ());
66 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
70 Beam::get_multiplicity (Grob *me)
73 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
75 Grob *sc = unsmob_grob (ly_car (s));
77 if (Stem::has_interface (sc))
78 m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
83 /* After pre-processing all directions should be set.
84 Several post-processing routines (stem, slur, script) need stem/beam
86 Currenly, this means that beam has set all stem's directions.
87 [Alternatively, stems could set its own directions, according to
88 their beam, during 'final-pre-processing'.] */
89 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
91 Beam::before_line_breaking (SCM smob)
93 Grob *me = unsmob_grob (smob);
95 /* Beams with less than 2 two stems don't make much sense, but could happen
100 For a beam that only has one stem, we try to do some disappearance magic:
101 we revert the flag, and move on to The Eternal Engraving Fields. */
103 int count = visible_stem_count (me);
106 me->warning (_ ("beam has less than two visible stems"));
108 SCM stems = me->get_grob_property ("stems");
109 if (scm_ilength (stems) == 1)
111 me->warning (_ ("Beam has less than two stems. Removing beam."));
113 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
116 return SCM_UNSPECIFIED;
118 else if (scm_ilength (stems) == 0)
121 return SCM_UNSPECIFIED;
126 if (!Directional_element_interface::get (me))
127 Directional_element_interface::set (me, get_default_dir (me));
129 consider_auto_knees (me);
130 set_stem_directions (me);
131 set_stem_shorten (me);
137 Beam::get_default_dir (Grob *me)
139 Drul_array<int> total;
140 total[UP] = total[DOWN] = 0;
141 Drul_array<int> count;
142 count[UP] = count[DOWN] = 0;
145 Link_array<Item> stems=
146 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
148 for (int i=0; i <stems.size (); i++)
151 Direction sd = Directional_element_interface::get (s);
152 int current = sd ? (1 + d * sd)/2
153 : Stem::get_center_distance (s, (Direction)-d);
160 } while (flip (&d) != DOWN);
162 SCM func = me->get_grob_property ("dir-function");
163 SCM s = gh_call2 (func,
164 gh_cons (gh_int2scm (count[UP]),
165 gh_int2scm (count[DOWN])),
166 gh_cons (gh_int2scm (total[UP]),
167 gh_int2scm (total[DOWN])));
169 if (gh_number_p (s) && gh_scm2int (s))
172 /* If dir is not determined: get default */
173 return to_dir (me->get_grob_property ("neutral-direction"));
177 /* Set all stems with non-forced direction to beam direction.
178 Urg: non-forced should become `without/with unforced' direction,
179 once stem gets cleaned-up. */
181 Beam::set_stem_directions (Grob *me)
183 Link_array<Item> stems
184 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
185 Direction d = Directional_element_interface::get (me);
187 for (int i=0; i <stems.size (); i++)
190 SCM force = s->remove_grob_property ("dir-forced");
191 if (!gh_boolean_p (force) || !gh_scm2bool (force))
192 Directional_element_interface::set (s, d);
196 /* Simplistic auto-knees; only consider vertical gap between two
199 `Forced' stem directions are ignored. If you don't want auto-knees,
200 don't set, or unset auto-knee-gap. */
202 Beam::consider_auto_knees (Grob *me)
204 SCM scm = me->get_grob_property ("auto-knee-gap");
206 if (gh_number_p (scm))
210 Real staff_space = Staff_symbol_referencer::staff_space (me);
211 Real gap = gh_scm2double (scm) / staff_space;
213 Direction d = Directional_element_interface::get (me);
214 Link_array<Item> stems=
215 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
217 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
218 for (int i=1; i < stems.size (); i++)
219 if (!Stem::invisible_b (stems[i]))
220 common = common->common_refpoint (stems[i], Y_AXIS);
223 for (int i=1; i < stems.size (); i++)
225 if (!Stem::invisible_b (stems[i-1]))
227 if (Stem::invisible_b (stems[l]))
229 if (Stem::invisible_b (stems[i]))
232 Real left = Stem::extremal_heads (stems[l])[d]
233 ->relative_coordinate (common, Y_AXIS);
234 Real right = Stem::extremal_heads (stems[i])[-d]
235 ->relative_coordinate (common, Y_AXIS);
237 Real dy = right - left;
241 knee_y = (right + left) / 2;
249 for (int i=0; i < stems.size (); i++)
251 if (Stem::invisible_b (stems[i]))
254 Real y = Stem::extremal_heads (stems[i])[d]
255 ->relative_coordinate (common, Y_AXIS);
257 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
258 s->set_grob_property ("dir-forced", SCM_BOOL_T);
264 /* Set stem's shorten property if unset.
266 take some y-position (chord/beam/nearest?) into account
267 scmify forced-fraction */
269 Beam::set_stem_shorten (Grob *m)
271 Spanner*me = dynamic_cast<Spanner*> (m);
273 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
275 int multiplicity = get_multiplicity (me);
277 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
278 if (shorten == SCM_EOL)
281 int sz = scm_ilength (shorten);
283 Real staff_space = Staff_symbol_referencer::staff_space (me);
284 SCM shorten_elt = scm_list_ref (shorten,
285 gh_int2scm (multiplicity <? (sz - 1)));
286 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
288 /* your similar cute comment here */
289 shorten_f *= forced_fraction;
291 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
294 /* Call list of y-dy-callbacks, that handle setting of
295 grob-properties y, dy.
297 User may set grob-properties: y-position-hs and height-hs
298 (to be fixed) that override the calculated y and dy.
300 Because y and dy cannot be calculated and quanted separately, we
301 always calculate both, then check for user override. */
302 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
304 Beam::after_line_breaking (SCM smob)
306 Grob *me = unsmob_grob (smob);
308 /* Copy to mutable list. */
309 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
310 me->set_grob_property ("positions", s);
312 if (ly_car (s) != SCM_BOOL_F)
313 return SCM_UNSPECIFIED;
315 SCM callbacks = me->get_grob_property ("position-callbacks");
316 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
317 gh_call1 (ly_car (i), smob);
319 return SCM_UNSPECIFIED;
330 MAKE_SCHEME_CALLBACK (Beam, new_quanting, 1);
332 Beam::new_quanting (SCM smob)
334 Grob *me = unsmob_grob (smob);
336 SCM s = me->get_grob_property ("positions");
337 Real yl = gh_scm2double (gh_car(s));
338 Real yr = gh_scm2double (gh_cdr(s));
340 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
341 // Staff_symbol_referencer::staff_space (me);
342 Real sthick = me->paper_l()->get_var ("stafflinethickness");
343 // * Staff_symbol_referencer::staff_space (me);
345 Real quants [] = {0.0, thick/2 - sthick / 2, 0.5, 1.0 - thick/2 + sthick/2};
346 int num_quants = int(sizeof(quants)/sizeof (Real));
350 const int REGION_SIZE = 3;
352 for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
353 for (int j = 0; j < num_quants; j++)
355 quantsl.push (i + quants[j] + int (yl));
356 quantsr.push (i + quants[j] + int (yr));
359 Array<Quant_score> qscores;
361 for(int l =0; l < quantsl.size(); l++)
362 for(int r =0; r < quantsr.size(); r++)
373 SCM score_funcs = me->get_grob_property("quant-score-functions");
374 for (SCM s = score_funcs; gh_pair_p (s); s = gh_cdr (s))
377 for (int i = qscores.size(); i--;)
379 if (qscores[i].demerits < 1000)
381 SCM score = gh_call3 (f,
383 gh_double2scm (qscores[i].yl),
384 gh_double2scm (qscores[i].yr));
386 qscores[i].demerits += gh_scm2double (score);
393 for (int i = qscores.size(); i--;)
395 if (qscores[i].demerits < best)
397 best = qscores [i].demerits ;
403 me->set_grob_property ("positions",
404 gh_cons (gh_double2scm (qscores[best_idx].yl),
405 gh_double2scm (qscores[best_idx].yr))
407 me->set_grob_property ("quant-score",gh_double2scm (qscores[best_idx].demerits));
409 return SCM_UNSPECIFIED;
412 MAKE_SCHEME_CALLBACK (Beam, score_slopes_dy, 3);
414 Beam::score_slopes_dy (SCM smob, SCM syl, SCM syr)
416 Grob*me = unsmob_grob(smob);
417 Real yl = gh_scm2double (syl);
418 Real yr = gh_scm2double (syr);
421 SCM sdy = me->get_grob_property("least-squares-dy");
422 SCM posns = me->get_grob_property ("positions");
424 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
425 Real dy_damp = - gh_scm2double (gh_car(posns)) + gh_scm2double (gh_cdr (posns));
428 if (sign (dy_damp) != sign (dy))
433 dem += 400* (0 >? (fabs(dy) - fabs(dy_mus)));
436 dem += shrink_extra_weight (fabs (dy_damp) - fabs(dy))* 10;
437 return gh_double2scm (dem);
440 MAKE_SCHEME_CALLBACK (Beam, score_stem_lengths, 3);
442 Beam::score_stem_lengths (SCM smob, SCM syl, SCM syr)
444 Grob*me = unsmob_grob(smob);
445 Real yl = gh_scm2double (syl);
446 Real yr = gh_scm2double (syr);
448 Link_array<Item> stems=
449 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
451 Real demerit_score = 0.0 ;
453 for (int i=0; i < stems.size (); i++)
456 if (Stem::invisible_b (s))
459 Real current_y = calc_stem_y_f (me, s, Interval(yl,yr));
460 Stem_info info = Stem::calc_stem_info (s);
461 Direction d = Directional_element_interface::get (s);
463 demerit_score += 500 * ( 0 >? (info.miny_f_ - d*current_y));
464 demerit_score += 500 * ( 0 >? (d * current_y - info.maxy_f_));
466 demerit_score += 5 * shrink_extra_weight (d * current_y - info.idealy_f_);
469 demerit_score *= 2.0 /stems.size();
471 return gh_double2scm (demerit_score);
481 MAKE_SCHEME_CALLBACK (Beam, score_forbidden_quants, 3);
483 Beam::score_forbidden_quants (SCM smob, SCM syl, SCM syr)
485 Grob*me = unsmob_grob(smob);
486 Real yl = gh_scm2double (syl);
487 Real yr = gh_scm2double (syr);
488 Real rad = Staff_symbol_referencer::staff_radius (me);
490 if (fabs (yl) < 2*rad && fabs( my_modf(yl) - 0.5) < 1e-3)
492 if (fabs (yr) < 2*rad && fabs( my_modf(yr) - 0.5) < 1e-3)
496 int multiplicity = get_multiplicity (me);
497 // todo: use multiplicity of outer stems.
498 if (multiplicity >= 2)
500 Real slt = me->paper_l()->get_var("stafflinethickness");
501 Real ss = Staff_symbol_referencer::staff_space(me);
502 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) * ss;
504 Real beam_space= (2*ss + slt - 3 *thickness) / 2.0;
505 if (multiplicity >= 4)
507 beam_space = (3*ss + slt - 4 * thickness) /3.0;
510 Direction d = Directional_element_interface::get (me);
511 yl += d * (beam_space + thickness);
512 yr += d * (beam_space + thickness);
513 if (fabs (yl) < rad && fabs( my_modf(yl) - 0.5) < 0.1)
515 if (fabs (yr) < rad && fabs( my_modf(yr) - 0.5) < 0.1)
519 return gh_double2scm ( dem);
524 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
526 Beam::least_squares (SCM smob)
528 Grob *me = unsmob_grob (smob);
530 int count = visible_stem_count (me);
535 me->set_grob_property ("positions", ly_interval2scm (pos));
536 return SCM_UNSPECIFIED;
539 Direction dir = Directional_element_interface::get (me);
541 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).idealy_f_,
542 Stem::calc_stem_info (last_visible_stem (me)).idealy_f_);
546 Interval chord (Stem::chord_start_f (first_visible_stem (me)),
547 Stem::chord_start_f (last_visible_stem (me)));
551 TODO : use scoring for this.
553 complicated, because we take stem-info.ideal for determining
557 /* Make simple beam on middle line have small tilt */
558 if (!ideal[LEFT] && chord.delta () && count == 2)
560 Direction d = (Direction)(sign (chord.delta ()) * dir);
561 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2
574 Array<Offset> ideals;
576 // ugh -> use commonx
577 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
578 Link_array<Item> stems=
579 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
581 for (int i=0; i < stems.size (); i++)
584 if (Stem::invisible_b (s))
586 ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
587 Stem::calc_stem_info (s).idealy_f_));
591 minimise_least_squares (&dydx, &y, ideals);
593 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
595 me->set_grob_property ("least-squares-dy", gh_double2scm (dy * dir));
597 pos = Interval (y*dir, (y+dy) * dir);
600 me->set_grob_property ("positions", ly_interval2scm (pos));
601 return SCM_UNSPECIFIED;
604 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
606 Beam::check_concave (SCM smob)
608 Grob *me = unsmob_grob (smob);
610 Link_array<Item> stems =
611 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
613 for (int i = 0; i < stems.size ();)
615 if (Stem::invisible_b (stems[i]))
621 if (stems.size () < 3)
622 return SCM_UNSPECIFIED;
624 /* Concaveness try #2: Sum distances of inner noteheads that
625 fall outside the interval of the two outer noteheads */
627 Interval iv (Stem::chord_start_f (stems[0]),
628 Stem::chord_start_f (stems.top ()));
630 if (iv[MAX] < iv[MIN])
633 for (int i = 1; i < stems.size () - 1; i++)
636 Real f = Stem::chord_start_f (stems[i]);
637 if ((c = f - iv[MAX]) > 0)
639 else if ((c = f - iv[MIN]) < 0)
643 Direction dir = Directional_element_interface::get (me);
646 Real concaveness = concave / (stems.size () - 2);
647 /* ugh: this is the a kludge to get input/regression/beam-concave.ly
648 to behave as baerenreiter. */
649 concaveness /= (stems.size () - 2);
651 Real r = gh_scm2double (me->get_grob_property ("concaveness-threshold"));
653 /* TODO: some sort of damping iso -> plain horizontal */
656 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
657 Real r = pos.linear_combination (0);
658 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
659 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
662 return SCM_UNSPECIFIED;
665 /* This neat trick is by Werner Lemberg,
666 damped = tanh (slope)
667 corresponds with some tables in [Wanske] CHECKME */
668 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
670 Beam::slope_damping (SCM smob)
672 Grob *me = unsmob_grob (smob);
674 if (visible_stem_count (me) <= 1)
675 return SCM_UNSPECIFIED;
677 SCM s = me->get_grob_property ("damping");
678 int damping = gh_scm2int (s);
682 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
683 Real dy = pos.delta ();
685 // ugh -> use commonx
686 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
687 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
688 Real dydx = dy && dx ? dy/dx : 0;
689 dydx = 0.6 * tanh (dydx) / damping;
691 Real damped_dy = dydx * dx;
692 pos[LEFT] += (dy - damped_dy) / 2;
693 pos[RIGHT] -= (dy - damped_dy) / 2;
695 me->set_grob_property ("positions", ly_interval2scm (pos));
697 return SCM_UNSPECIFIED;
700 /* Prevent interference from stafflines. */
702 Beam::quantise_interval (Grob *me, Interval pos, Direction quant_dir)
704 int multiplicity = get_multiplicity (me);
706 Real staff_space = Staff_symbol_referencer::staff_space (me);
707 Real thick = me->paper_l ()->get_var ("stafflinethickness");
708 Direction dir = Directional_element_interface::get (me);
709 Real dy = pos.delta ();
711 Drul_array<Interval> bounds;
716 ? me->get_grob_property ("left-position-quant-function")
717 : me->get_grob_property ("right-position-quant-function");
719 SCM quants = scm_apply (proc,
721 scm_list_n (gh_int2scm (multiplicity),
724 gh_double2scm (thick / staff_space),
730 for (SCM i = quants; gh_pair_p (i); i = ly_cdr (i))
731 a.push (gh_scm2double (ly_car (i)));
736 bounds[d] = quantise_iv (a, pos[d]*dir/staff_space) * staff_space;
738 while (flip (&d) != LEFT);
742 // quant direction hints disabled for now
743 int q = 0;//(int)quant_dir;
745 /* TODO: make smart choice, find best left/right quants pair.
747 Slope should never be steeper than least_squares (before damping)
749 Slope should never be reduced to zero.
751 SCM s = me->get_grob_property ("least-squares-dy");
752 Real lsdy = gh_number_p (s) ? gh_scm2double (s) : 0;
754 // Interval qpos (0, 1000 * sign (dy));
756 Real epsilon = staff_space / 10;
757 Direction ldir = LEFT;
760 Direction rdir = LEFT;
763 Interval i (bounds[LEFT][ldir]*dir, bounds[RIGHT][rdir]*dir);
765 || (abs (i.delta ()) <= abs (lsdy) + epsilon
766 && sign (i.delta ()) == sign (lsdy)))
767 && (abs (abs (i.delta ()) - ady)
768 <= abs (abs (qpos.delta ()) - ady))
769 && sign (i.delta ()) == sign (pos.delta ())
771 || (i[LEFT]*q >= pos[LEFT]*q && i[RIGHT]*q
775 while (flip (&rdir) != LEFT);
777 while (flip (&ldir) != LEFT);
783 /* Quantise vertical position (left and right) of beam.
784 Generalisation of [Ross]. */
785 MAKE_SCHEME_CALLBACK (Beam, quantise_position, 1);
787 Beam::quantise_position (SCM smob)
789 Grob *me = unsmob_grob (smob);
791 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
792 Real y_shift = check_stem_length_f (me, pos);
794 Real staff_space = Staff_symbol_referencer::staff_space (me);
796 Direction dir = Directional_element_interface::get (me);
797 for (int i = 0; i < 10; i++)
799 Interval qpos = quantise_interval (me, pos, CENTER);
800 // how to check for uninitised interval, (inf, -inf)?
801 if (qpos[LEFT] < 1000)
803 y_shift = check_stem_length_f (me, qpos);
804 if (y_shift * dir < staff_space / 2)
810 pos += ((i + 1) * ((i % 2) * -2 + 1)) * dir * staff_space / 4;
814 me->set_grob_property ("positions", ly_interval2scm (pos));
815 set_stem_lengths (me);
818 pos = ly_scm2interval (me->get_grob_property ("positions"));
820 y_shift = check_stem_length_f (me, pos);
822 Real half_space = Staff_symbol_referencer::staff_space (me) / 2;
824 if (y_shift > half_space / 4)
828 /* for significantly lengthened or shortened stems,
829 request quanting the other way.
831 if (abs (y_shift) > half_space / 2)
832 quant_dir = sign (y_shift) * Directional_element_interface::get (me);
833 pos = quantise_interval (me, pos, (Direction)quant_dir);
836 me->set_grob_property ("positions", ly_interval2scm (pos));
839 return SCM_UNSPECIFIED;
842 MAKE_SCHEME_CALLBACK (Beam, end_after_line_breaking, 1);
844 Beam::end_after_line_breaking (SCM smob)
846 Grob *me = unsmob_grob (smob);
847 set_stem_lengths (me);
849 return SCM_UNSPECIFIED;
853 Calculate the Y position of the stem-end, given the Y-left, Y-right
854 in POS, and for stem S.
857 Beam::calc_stem_y_f (Grob *me, Item* s, Interval pos)
859 int beam_multiplicity = get_multiplicity (me);
860 int stem_multiplicity = (Stem::flag_i (s) - 2) >? 0;
862 SCM space_proc = me->get_grob_property ("space-function");
863 SCM space = gh_call1 (space_proc, gh_int2scm (beam_multiplicity));
865 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
866 Real interbeam_f = gh_scm2double (space);
868 // ugh -> use commonx
869 Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
870 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
871 Real dy = pos.delta ();
872 Real stem_y = (dy && dx
873 ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
878 Direction dir = Directional_element_interface::get (me);
879 Direction sdir = Directional_element_interface::get (s);
884 stem_y -= dir * (thick / 2 + (beam_multiplicity - 1) * interbeam_f);
886 // huh, why not for first visible?
887 if (Staff_symbol_referencer::staff_symbol_l (s)
888 != Staff_symbol_referencer::staff_symbol_l (last_visible_stem (me)))
889 stem_y += Directional_element_interface::get (me)
890 * (beam_multiplicity - stem_multiplicity) * interbeam_f;
896 /* Make very sure that we don't have stems that are too short.
897 Try our best not to have stems that are too long (think: knees).
899 Optionally (testing): try to lengthen more, to reach more ideal
902 Beam::check_stem_length_f (Grob *me, Interval pos)
906 Direction dir = Directional_element_interface::get (me);
908 Link_array<Item> stems=
909 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
912 int ideal_lengthen_count = 0;
913 Real ideal_lengthen = 0;
914 int ideal_shorten_count = 0;
915 Real ideal_shorten = 0;
917 for (int i=0; i < stems.size (); i++)
920 if (Stem::invisible_b (s))
923 knee |= dir != Directional_element_interface::get (s);
925 Real stem_y = calc_stem_y_f (me, s, pos);
928 Stem_info info = Stem::calc_stem_info (s);
930 shorten = shorten <? info.maxy_f_ - stem_y;
931 lengthen = lengthen >? info.miny_f_ - stem_y;
933 if (info.idealy_f_ - stem_y > 0)
936 ideal_lengthen += info.idealy_f_ - stem_y;
937 ideal_lengthen_count++;
939 ideal_lengthen = ideal_lengthen >? info.idealy_f_ - stem_y;
940 ideal_lengthen_count = 1;
943 else if (info.idealy_f_ - stem_y < 0)
946 ideal_shorten += info.idealy_f_ - stem_y;
947 ideal_shorten_count++;
949 ideal_shorten = ideal_shorten <? info.idealy_f_ - stem_y;
950 ideal_shorten_count = 1;
955 if (lengthen && shorten)
956 me->warning (_ ("weird beam vertical offset"));
958 if (ideal_lengthen_count)
959 lengthen = (ideal_lengthen / ideal_lengthen_count) >? lengthen;
960 if (knee && ideal_shorten_count)
961 shorten = (ideal_shorten / ideal_shorten_count) <? shorten;
963 if (lengthen && shorten)
964 return dir * (lengthen + shorten);
966 return dir * (shorten ? shorten : lengthen);
970 Hmm. At this time, beam position and slope are determined. Maybe,
971 stem directions and length should set to relative to the chord's
972 position of the beam. */
974 Beam::set_stem_lengths (Grob *me)
976 Link_array<Item> stems=
977 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
979 if (stems.size () <= 1)
982 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
983 for (int i=1; i < stems.size (); i++)
984 if (!Stem::invisible_b (stems[i]))
985 common = common->common_refpoint (stems[i], Y_AXIS);
987 Direction dir = Directional_element_interface::get (me);
988 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
989 Real staff_space = Staff_symbol_referencer::staff_space (me);
990 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
991 bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
992 for (int i=0; i < stems.size (); i++)
995 if (Stem::invisible_b (s))
998 Real stem_y = calc_stem_y_f (me, s, pos);
1000 // doesn't play well with dvips
1002 if (Stem::get_direction (s) == dir)
1003 stem_y += Stem::get_direction (s) * thick / 2;
1005 /* caution: stem measures in staff-positions */
1006 Real id = me->relative_coordinate (common, Y_AXIS)
1007 - stems[i]->relative_coordinate (common, Y_AXIS);
1008 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1013 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1015 Link_array<Grob> stems=
1016 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1019 for (int i=0; i < stems.size (); i++)
1023 /* Don't overwrite user override (?) */
1024 if (Stem::beam_count (stems[i], d) == -1
1025 /* Don't set beaming for outside of outer stems */
1026 && ! (d == LEFT && i == 0)
1027 && ! (d == RIGHT && i == stems.size () -1))
1029 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1030 Stem::set_beaming (stems[i], b, d);
1033 while (flip (&d) != LEFT);
1040 beams to go with one stem.
1045 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1047 // ugh -> use commonx
1049 && !(next->relative_coordinate (0, X_AXIS)
1050 > here->relative_coordinate (0, X_AXIS)))
1052 && !(prev->relative_coordinate (0, X_AXIS)
1053 < here->relative_coordinate (0, X_AXIS))))
1054 programming_error ("Beams are not left-to-right");
1056 int multiplicity = get_multiplicity (me);
1058 SCM space_proc = me->get_grob_property ("space-function");
1059 SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
1061 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1062 Real interbeam_f = gh_scm2double (space);
1064 Real bdy = interbeam_f;
1067 Molecule rightbeams;
1070 if (!Stem::first_head (here))
1073 int t = Stem::type_i (here);
1075 SCM proc = me->get_grob_property ("flag-width-function");
1076 SCM result = gh_call1 (proc, gh_int2scm (t));
1077 nw_f = gh_scm2double (result);
1081 Direction dir = Directional_element_interface::get (me);
1083 /* [Tremolo] beams on whole notes may not have direction set? */
1085 dir = Directional_element_interface::get (here);
1088 /* half beams extending to the left. */
1091 int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1092 - Stem::beam_count (prev, RIGHT);
1093 int lwholebeams= Stem::beam_count (here, LEFT)
1094 <? Stem::beam_count (prev, RIGHT);
1096 /* Half beam should be one note-width,
1097 but let's make sure two half-beams never touch */
1099 // FIXME: TODO (check) stem width / sloped beams
1100 Real w = here->relative_coordinate (0, X_AXIS)
1101 - prev->relative_coordinate (0, X_AXIS);
1102 Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1104 * me->paper_l ()->get_var ("stafflinethickness");
1108 if (lhalfs) // generates warnings if not
1109 a = Lookup::beam (dydx, w + stem_w, thick);
1110 a.translate (Offset (-w, -w * dydx));
1111 a.translate_axis (-stem_w/2, X_AXIS);
1112 for (int j = 0; j < lhalfs; j++)
1115 b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1116 leftbeams.add_molecule (b);
1122 int rhalfs = Stem::beam_count (here, RIGHT)
1123 - Stem::beam_count (next, LEFT);
1124 int rwholebeams= Stem::beam_count (here, RIGHT)
1125 <? Stem::beam_count (next, LEFT);
1127 Real w = next->relative_coordinate (0, X_AXIS)
1128 - here->relative_coordinate (0, X_AXIS);
1130 Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1132 * me->paper_l ()->get_var ("stafflinethickness");
1134 Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1135 a.translate_axis (- stem_w/2, X_AXIS);
1139 SCM gap = me->get_grob_property ("gap");
1140 if (gh_number_p (gap))
1142 int gap_i = gh_scm2int ((gap));
1143 int nogap = rwholebeams - gap_i;
1145 for (; j < nogap; j++)
1148 b.translate_axis (-dir * bdy * j, Y_AXIS);
1149 rightbeams.add_molecule (b);
1151 if (Stem::invisible_b (here))
1156 a = Lookup::beam (dydx, w + stem_w, thick);
1159 for (; j < rwholebeams; j++)
1163 if (Stem::invisible_b (here))
1164 // ugh, see chord-tremolo.ly
1165 tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1168 b.translate (Offset (tx, -dir * bdy * j));
1169 rightbeams.add_molecule (b);
1174 a = Lookup::beam (dydx, w, thick);
1176 for (; j < rwholebeams + rhalfs; j++)
1179 b.translate_axis (- dir * bdy * j, Y_AXIS);
1180 rightbeams.add_molecule (b);
1184 leftbeams.add_molecule (rightbeams);
1186 /* Does beam quanting think of the asymetry of beams?
1187 Refpoint is on bottom of symbol. (FIXTHAT) --hwn. */
1192 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1194 Beam::brew_molecule (SCM smob)
1196 Grob *me =unsmob_grob (smob);
1199 if (!gh_pair_p (me->get_grob_property ("stems")))
1202 Link_array<Item>stems =
1203 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1204 if (visible_stem_count (me))
1206 // ugh -> use commonx
1207 x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1208 dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1212 x0 = stems[0]->relative_coordinate (0, X_AXIS);
1213 dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1216 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1217 Real dy = pos.delta ();
1218 Real dydx = dy && dx ? dy/dx : 0;
1220 for (int i=0; i < stems.size (); i++)
1222 Item *item = stems[i];
1223 Item *prev = (i > 0)? stems[i-1] : 0;
1224 Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1226 Molecule sb = stem_beams (me, item, next, prev, dydx);
1227 Real x = item->relative_coordinate (0, X_AXIS) - x0;
1228 sb.translate (Offset (x, x * dydx + pos[LEFT]));
1229 mol.add_molecule (sb);
1232 mol.translate_axis (x0
1233 - dynamic_cast<Spanner*> (me)
1234 ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1240 This code prints the demerits for each beam. Perhaps this
1241 should be switchable for those who want to twiddle with the
1244 SCM dem = me->get_grob_property ("quant-score");
1245 Real s = gh_scm2double (dem);
1246 String str = to_str (s);
1248 SCM properties = Font_interface::font_alist_chain (me);
1250 Molecule tm = Text_item::text2molecule (me, gh_str02scm (str.ch_C()), properties);
1251 mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1254 return mol.smobbed_copy ();
1258 Beam::forced_stem_count (Grob *me)
1260 Link_array<Item>stems =
1261 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1263 for (int i=0; i < stems.size (); i++)
1267 if (Stem::invisible_b (s))
1270 if (((int)Stem::chord_start_f (s))
1271 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1281 use filter and standard list functions.
1284 Beam::visible_stem_count (Grob *me)
1286 Link_array<Item>stems =
1287 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1289 for (int i = stems.size (); i--;)
1291 if (!Stem::invisible_b (stems[i]))
1298 Beam::first_visible_stem (Grob *me)
1300 Link_array<Item>stems =
1301 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1303 for (int i = 0; i < stems.size (); i++)
1305 if (!Stem::invisible_b (stems[i]))
1312 Beam::last_visible_stem (Grob *me)
1314 Link_array<Item>stems =
1315 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1316 for (int i = stems.size (); i--;)
1318 if (!Stem::invisible_b (stems[i]))
1327 handle rest under beam (do_post: beams are calculated now)
1328 what about combination of collisions and rest under beam.
1332 rest -> stem -> beam -> interpolate_y_position ()
1334 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1336 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1338 Grob *rest = unsmob_grob (element_smob);
1339 Axis a = (Axis) gh_scm2int (axis);
1341 assert (a == Y_AXIS);
1343 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1346 return gh_double2scm (0.0);
1347 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1349 || !Beam::has_interface (beam)
1350 || !Beam::visible_stem_count (beam))
1351 return gh_double2scm (0.0);
1353 // make callback for rest from this.
1354 // todo: make sure this calced already.
1356 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1357 Interval pos (0, 0);
1358 SCM s = beam->get_grob_property ("positions");
1359 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1360 pos = ly_scm2interval (s);
1362 Real dy = pos.delta ();
1363 // ugh -> use commonx
1364 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1365 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1366 Real dydx = dy && dx ? dy/dx : 0;
1368 Direction d = Stem::get_direction (stem);
1369 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1371 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1374 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1377 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1379 minimum_dist + -d * (beamy - rest_dim) >? 0;
1381 int stafflines = Staff_symbol_referencer::line_count (rest);
1383 // move discretely by half spaces.
1384 int discrete_dist = int (ceil (dist));
1386 // move by whole spaces inside the staff.
1387 if (discrete_dist < stafflines+1)
1388 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1390 return gh_double2scm (-d * discrete_dist);
1395 Beam::has_interface (Grob *me)
1397 return me->has_interface (ly_symbol2scm ("beam-interface"));