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>
17 * Use Number_pair i.s.o Interval to represent (yl, yr).
19 - Determine auto knees based on positions if it's set by the user.
25 - Stems run to the Y-center of the beam.
27 - beam_space is the offset between Y centers of the beam.
32 #include <math.h> // tanh.
34 #include "align-interface.hh"
35 #include "molecule.hh"
36 #include "directional-element-interface.hh"
40 #include "least-squares.hh"
42 #include "paper-def.hh"
44 #include "group-interface.hh"
45 #include "staff-symbol-referencer.hh"
51 #define DEBUG_QUANTING 0
55 #include "text-item.hh" // debug output.
56 #include "font-interface.hh" // debug output.
60 const int INTER_QUANT_PENALTY = 1000;
61 const int SECONDARY_BEAM_DEMERIT = 15;
62 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
63 // possibly ridiculous, but too short stems just won't do
64 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
65 const int DAMPING_DIRECTIION_PENALTY = 800;
66 const int MUSICAL_DIRECTION_FACTOR = 400;
67 const int IDEAL_SLOPE_FACTOR = 10;
68 const int REGION_SIZE = 2;
72 shrink_extra_weight (Real x)
74 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
78 Beam::add_stem (Grob *me, Grob *s)
80 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
82 s->add_dependency (me);
84 assert (!Stem::beam_l (s));
85 s->set_grob_property ("beam", me->self_scm ());
87 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
91 Beam::get_beam_space (Grob *me)
93 SCM func = me->get_grob_property ("space-function");
94 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
95 return gh_scm2double (s);
102 Beam::get_beam_count (Grob *me)
105 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
107 Grob *sc = unsmob_grob (ly_car (s));
109 m = m >? (Stem::beam_multiplicity (sc).length () + 1);
114 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
116 Beam::space_function (SCM smob, SCM beam_count)
118 Grob *me = unsmob_grob (smob);
120 Real staff_space = Staff_symbol_referencer::staff_space (me);
121 Real line = me->paper_l ()->get_var ("linethickness");
122 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
125 Real beam_space = gh_scm2int (beam_count) < 4
126 ? (2*staff_space + line - thickness) / 2.0
127 : (3*staff_space + line - thickness) / 3.0;
129 return gh_double2scm (beam_space);
133 /* After pre-processing all directions should be set.
134 Several post-processing routines (stem, slur, script) need stem/beam
136 Currenly, this means that beam has set all stem's directions.
137 [Alternatively, stems could set its own directions, according to
138 their beam, during 'final-pre-processing'.] */
139 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
141 Beam::before_line_breaking (SCM smob)
143 Grob *me = unsmob_grob (smob);
145 /* Beams with less than 2 two stems don't make much sense, but could happen
150 For a beam that only has one stem, we try to do some disappearance magic:
151 we revert the flag, and move on to The Eternal Engraving Fields. */
153 int count = visible_stem_count (me);
156 me->warning (_ ("beam has less than two visible stems"));
158 SCM stems = me->get_grob_property ("stems");
159 if (scm_ilength (stems) == 1)
161 me->warning (_ ("Beam has less than two stems. Removing beam."));
163 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
166 return SCM_UNSPECIFIED;
168 else if (scm_ilength (stems) == 0)
171 return SCM_UNSPECIFIED;
176 Direction d = get_default_dir (me);
178 consider_auto_knees (me, d);
179 set_stem_directions (me, d);
183 set_stem_shorten (me);
192 Beam::connect_beams (Grob *me)
194 Link_array<Grob> stems=
195 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
198 last_int.set_empty();
199 for (int i = 0; i< stems.size(); i++)
201 Grob *this_stem = stems[i];
202 SCM this_beaming = this_stem->get_grob_property ("beaming");
204 Direction this_dir = Directional_element_interface::get(this_stem);
207 int start_point = last_int [this_dir];
213 if (d == RIGHT && i == stems.size()-1)
216 new_slice.set_empty();
217 SCM s = index_get_cell (this_beaming, d);
218 for (; gh_pair_p (s); s = gh_cdr (s))
221 start_point - this_dir * gh_scm2int (gh_car (s));
223 new_slice.add_point (new_beam_pos);
224 gh_set_car_x (s, gh_int2scm (new_beam_pos));
227 while (flip (&d) != LEFT);
229 if (!new_slice.empty_b())
230 last_int = new_slice;
234 SCM s = gh_cdr (this_beaming);
235 for (; gh_pair_p (s); s = gh_cdr (s))
237 int np = - this_dir * gh_scm2int (gh_car(s));
238 gh_set_car_x (s, gh_int2scm (np));
239 last_int.add_point (np);
245 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
247 Beam::brew_molecule (SCM grob)
249 Grob *me = unsmob_grob (grob);
250 Link_array<Grob> stems=
251 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
252 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
255 if (visible_stem_count (me))
257 // ugh -> use commonx
258 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
259 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
263 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
264 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
267 SCM posns = me->get_grob_property ("positions");
269 if (!ly_number_pair_p (posns))
271 programming_error ("No beam posns");
272 pos = Interval (0,0);
275 pos= ly_scm2interval (posns);
277 Real dy = pos.delta ();
278 Real dydx = dy && dx ? dy/dx : 0;
280 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
281 Real bdy = get_beam_space (me);
283 SCM last_beaming = SCM_EOL;;
284 Real last_xposn = -1;
285 Real last_width = -1 ;
288 SCM gap = me->get_grob_property ("gap");
290 Real lt = me->paper_l ()->get_var ("linethickness");
291 for (int i = 0; i< stems.size(); i++)
295 SCM this_beaming = st->get_grob_property ("beaming");
296 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
297 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
301 SCM left = gh_cdr (last_beaming);
302 SCM right = gh_car (this_beaming);
304 Array<int> fullbeams;
305 Array<int> lfliebertjes;
306 Array<int> rfliebertjes;
309 gh_pair_p (s); s =gh_cdr (s))
311 int b = gh_scm2int (gh_car (s));
312 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
318 lfliebertjes.push (b);
322 gh_pair_p (s); s =gh_cdr (s))
324 int b = gh_scm2int (gh_car (s));
325 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
327 rfliebertjes.push (b);
332 Real w = xposn - last_xposn;
333 Real stem_offset = 0.0;
334 Real width_corr = 0.0;
337 stem_offset -= last_width/2;
338 width_corr += last_width/2;
341 if (i == stems.size() -1)
343 width_corr += stem_width/2;
346 if (gh_number_p (gap))
348 Real g = gh_scm2double (gap);
353 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
354 for (int j = fullbeams.size(); j--;)
357 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
358 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
359 the_beam.add_molecule (b);
362 if (lfliebertjes.size() || rfliebertjes.size())
366 if (!Stem::first_head (st))
370 int t = Stem::duration_log (st);
372 SCM proc = me->get_grob_property ("flag-width-function");
373 SCM result = gh_call1 (proc, gh_int2scm (t));
374 nw_f = gh_scm2double (result);
377 /* Half beam should be one note-width,
378 but let's make sure two half-beams never touch */
380 Real w = xposn - last_xposn;
383 Molecule half = Lookup::beam (dydx, w, thick);
384 for (int j = lfliebertjes.size(); j--;)
387 b.translate_axis (last_xposn - x0, X_AXIS);
388 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
389 the_beam.add_molecule (b);
391 for (int j = rfliebertjes.size(); j--;)
394 b.translate_axis (xposn - x0 - w , X_AXIS);
395 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
396 the_beam.add_molecule (b);
402 last_width = stem_width;
403 last_beaming = this_beaming;
406 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
407 the_beam.translate_axis (pos[LEFT], Y_AXIS);
412 This code prints the demerits for each beam. Perhaps this
413 should be switchable for those who want to twiddle with the
419 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
422 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
425 SCM properties = Font_interface::font_alist_chain (me);
428 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
429 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
435 return the_beam.smobbed_copy();
442 Beam::get_default_dir (Grob *me)
444 Drul_array<int> total;
445 total[UP] = total[DOWN] = 0;
446 Drul_array<int> count;
447 count[UP] = count[DOWN] = 0;
450 Link_array<Item> stems=
451 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
453 for (int i=0; i <stems.size (); i++)
456 Direction sd = Directional_element_interface::get (s);
458 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
459 int current = sd ? (1 + d * sd)/2 : center_distance;
466 } while (flip (&d) != DOWN);
468 SCM func = me->get_grob_property ("dir-function");
469 SCM s = gh_call2 (func,
470 gh_cons (gh_int2scm (count[UP]),
471 gh_int2scm (count[DOWN])),
472 gh_cons (gh_int2scm (total[UP]),
473 gh_int2scm (total[DOWN])));
475 if (gh_number_p (s) && gh_scm2int (s))
478 /* If dir is not determined: get default */
479 return to_dir (me->get_grob_property ("neutral-direction"));
483 /* Set all stems with non-forced direction to beam direction.
484 Urg: non-forced should become `without/with unforced' direction,
485 once stem gets cleaned-up. */
487 Beam::set_stem_directions (Grob *me, Direction d)
489 Link_array<Item> stems
490 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
492 for (int i=0; i <stems.size (); i++)
495 SCM force = s->remove_grob_property ("dir-forced");
496 if (!gh_boolean_p (force) || !gh_scm2bool (force))
497 Directional_element_interface::set (s, d);
501 /* Simplistic auto-knees; only consider vertical gap between two
504 `Forced' stem directions are ignored. If you don't want auto-knees,
505 don't set, or unset auto-knee-gap. */
507 Beam::consider_auto_knees (Grob *me, Direction d)
509 SCM scm = me->get_grob_property ("auto-knee-gap");
511 if (gh_number_p (scm))
515 Real staff_space = Staff_symbol_referencer::staff_space (me);
516 Real gap = gh_scm2double (scm) / staff_space;
519 Link_array<Grob> stems=
520 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
522 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
525 for (int i=1; i < stems.size (); i++)
527 if (!Stem::invisible_b (stems[i-1]))
529 if (Stem::invisible_b (stems[l]))
531 if (Stem::invisible_b (stems[i]))
534 Real left = Stem::extremal_heads (stems[l])[d]
535 ->relative_coordinate (common, Y_AXIS);
536 Real right = Stem::extremal_heads (stems[i])[-d]
537 ->relative_coordinate (common, Y_AXIS);
539 Real dy = right - left;
543 knee_y = (right + left) / 2;
551 for (int i=0; i < stems.size (); i++)
554 if (Stem::invisible_b (s) ||
555 s->get_grob_property ("dir-forced") == SCM_BOOL_T)
557 Real y = Stem::extremal_heads (stems[i])[d]
558 ->relative_coordinate (common, Y_AXIS);
560 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
561 s->set_grob_property ("dir-forced", SCM_BOOL_T);
567 /* Set stem's shorten property if unset.
570 take some y-position (chord/beam/nearest?) into account
571 scmify forced-fraction
575 why is shorten stored in beam, and not directly in stem?
579 Beam::set_stem_shorten (Grob *m)
581 Spanner*me = dynamic_cast<Spanner*> (m);
583 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
585 int beam_count = get_beam_count (me);
587 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
588 if (shorten == SCM_EOL)
591 int sz = scm_ilength (shorten);
593 Real staff_space = Staff_symbol_referencer::staff_space (me);
594 SCM shorten_elt = scm_list_ref (shorten,
595 gh_int2scm (beam_count <? (sz - 1)));
596 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
598 /* your similar cute comment here */
599 shorten_f *= forced_fraction;
602 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
605 /* Call list of y-dy-callbacks, that handle setting of
609 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
611 Beam::after_line_breaking (SCM smob)
613 Grob *me = unsmob_grob (smob);
615 /* Copy to mutable list. */
616 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
617 me->set_grob_property ("positions", s);
619 if (ly_car (s) == SCM_BOOL_F)
622 // one wonders if such genericity is necessary --hwn.
623 SCM callbacks = me->get_grob_property ("position-callbacks");
624 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
625 gh_call1 (ly_car (i), smob);
628 set_stem_lengths (me);
629 return SCM_UNSPECIFIED;
643 - Make all demerits customisable
645 - One sensible check per demerit (what's this --hwn)
647 - Add demerits for quants per se, as to forbid a specific quant
651 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
653 Beam::quanting (SCM smob)
655 Grob *me = unsmob_grob (smob);
657 SCM s = me->get_grob_property ("positions");
658 Real yl = gh_scm2double (gh_car (s));
659 Real yr = gh_scm2double (gh_cdr (s));
661 Real ss = Staff_symbol_referencer::staff_space (me);
662 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
663 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
666 SCM sdy = me->get_grob_property ("least-squares-dy");
667 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
670 Real sit = (thickness - slt) / 2;
672 Real hang = 1.0 - (thickness - slt) / 2;
673 Real quants [] = {straddle, sit, inter, hang };
675 int num_quants = int (sizeof (quants)/sizeof (Real));
680 going to REGION_SIZE == 2, yields another 0.6 second with
684 (result indexes between 70 and 575) ? --hwn.
691 Do stem computations. These depend on YL and YR linearly, so we can
692 precompute for every stem 2 factors.
694 Link_array<Grob> stems=
695 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
696 Array<Stem_info> stem_infos;
697 Array<Real> lbase_lengths;
698 Array<Real> rbase_lengths;
700 Drul_array<bool> dirs_found(0,0);
701 Grob *common_y = common_refpoint_of_array (stems, me, Y_AXIS);
703 bool french = to_boolean (me->get_grob_property ("french-beaming"));
704 for (int i= 0; i < stems.size(); i++)
707 stem_infos.push (Stem::calc_stem_info (s));
708 dirs_found[stem_infos.top ().dir_] = true;
710 Real b = calc_stem_y (me, s, common_y , Interval (1,0), french && i > 0&& (i < stems.size () -1));
711 lbase_lengths.push (b);
713 Real a = calc_stem_y (me, s, common_y , Interval (0,1), french && i > 0&& (i < stems.size () -1));
714 rbase_lengths.push (a);
717 Direction ldir = Direction (stem_infos[0].dir_);
718 Direction rdir = Direction (stem_infos.top ().dir_);
719 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
722 int region_size = REGION_SIZE;
724 Knees are harder, lets try some more possibilities for knees.
729 for (int i = -region_size ; i < region_size; i++)
730 for (int j = 0; j < num_quants; j++)
732 quantsl.push (i + quants[j] + int (yl));
733 quantsr.push (i + quants[j] + int (yr));
736 Array<Quant_score> qscores;
738 for (int l =0; l < quantsl.size (); l++)
739 for (int r =0; r < quantsr.size (); r++)
751 This is a longish function, but we don't separate this out into
752 neat modular separate subfunctions, as the subfunctions would be
753 called for many values of YL, YR. By precomputing various
754 parameters outside of the loop, we can save a lot of time.
759 Grob *fvs = first_visible_stem (me);
760 Grob *lvs = last_visible_stem (me);
765 Grob *commony = fvs->common_refpoint (lvs, Y_AXIS);
766 xstaff = Align_interface::has_interface (commony);
769 for (int i = qscores.size (); i--;)
770 if (qscores[i].demerits < 100)
773 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
774 dy_mus, yr- yl, xstaff);
777 Real rad = Staff_symbol_referencer::staff_radius (me);
778 int beam_count = get_beam_count (me);
779 Real beam_space = beam_count < 4
780 ? (2*ss + slt - thickness) / 2.0
781 : (3*ss + slt - thickness) / 3.0;
783 for (int i = qscores.size (); i--;)
784 if (qscores[i].demerits < 100)
787 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
788 rad, slt, thickness, beam_space,
789 beam_count, ldir, rdir);
793 for (int i = qscores.size (); i--;)
794 if (qscores[i].demerits < 100)
797 += score_stem_lengths (stems, stem_infos,
798 lbase_lengths, rbase_lengths,
800 me, qscores[i].yl, qscores[i].yr);
806 for (int i = qscores.size (); i--;)
808 if (qscores[i].demerits < best)
810 best = qscores [i].demerits ;
816 me->set_grob_property ("positions",
817 gh_cons (gh_double2scm (qscores[best_idx].yl),
818 gh_double2scm (qscores[best_idx].yr))
824 me->set_grob_property ("quant-score",
825 gh_double2scm (qscores[best_idx].demerits));
826 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
829 return SCM_UNSPECIFIED;
833 Beam::score_stem_lengths (Link_array<Grob>stems,
834 Array<Stem_info> stem_infos,
835 Array<Real> left_factor,
836 Array<Real> right_factor,
841 Real demerit_score = 0.0 ;
842 Real pen = STEM_LENGTH_LIMIT_PENALTY;
844 for (int i=0; i < stems.size (); i++)
847 if (Stem::invisible_b (s))
851 yl * left_factor[i] + right_factor[i]* yr;
853 Stem_info info = stem_infos[i];
854 Direction d = info.dir_;
857 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
859 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
860 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
863 demerit_score *= 2.0 / stems.size ();
865 return demerit_score;
869 Beam::score_slopes_dy (Grob *me,
871 Real dy_mus, Real dy_damp,
877 if (sign (dy_damp) != sign (dy))
879 dem += DAMPING_DIRECTIION_PENALTY;
882 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
885 Real slope_penalty = IDEAL_SLOPE_FACTOR;
888 Xstaff beams tend to use extreme slopes to get short stems. We
889 put in a penalty here.
894 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* slope_penalty;
901 return x - floor (x);
905 Beam::score_forbidden_quants (Grob*me,
909 Real thickness, Real beam_space,
911 Direction ldir, Direction rdir)
916 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
917 dem += INTER_QUANT_PENALTY;
918 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
919 dem += INTER_QUANT_PENALTY;
921 // todo: use beam_count of outer stems.
926 Real sit = (thickness - slt) / 2;
928 Real hang = 1.0 - (thickness - slt) / 2;
931 if (fabs (yl - ldir * beam_space) < rad
932 && fabs (my_modf (yl) - inter) < 1e-3)
933 dem += SECONDARY_BEAM_DEMERIT;
934 if (fabs (yr - rdir * beam_space) < rad
935 && fabs (my_modf (yr) - inter) < 1e-3)
936 dem += SECONDARY_BEAM_DEMERIT;
941 Can't we simply compute the distance between the nearest
942 staffline and the secondary beam? That would get rid of the
943 silly case analysis here (which is probably not when we have
944 different beam-thicknesses.)
950 // hmm, without Interval/Drul_array, you get ~ 4x same code...
951 if (fabs (yl - ldir * beam_space) < rad + inter)
953 if (ldir == UP && dy <= eps
954 && fabs (my_modf (yl) - sit) < eps)
955 dem += SECONDARY_BEAM_DEMERIT;
957 if (ldir == DOWN && dy >= eps
958 && fabs (my_modf (yl) - hang) < eps)
959 dem += SECONDARY_BEAM_DEMERIT;
962 if (fabs (yr - rdir * beam_space) < rad + inter)
964 if (rdir == UP && dy >= eps
965 && fabs (my_modf (yr) - sit) < eps)
966 dem += SECONDARY_BEAM_DEMERIT;
968 if (rdir == DOWN && dy <= eps
969 && fabs (my_modf (yr) - hang) < eps)
970 dem += SECONDARY_BEAM_DEMERIT;
975 if (fabs (yl - 2 * ldir * beam_space) < rad + inter)
977 if (ldir == UP && dy <= eps
978 && fabs (my_modf (yl) - straddle) < eps)
979 dem += SECONDARY_BEAM_DEMERIT;
981 if (ldir == DOWN && dy >= eps
982 && fabs (my_modf (yl) - straddle) < eps)
983 dem += SECONDARY_BEAM_DEMERIT;
986 if (fabs (yr - 2 * rdir * beam_space) < rad + inter)
988 if (rdir == UP && dy >= eps
989 && fabs (my_modf (yr) - straddle) < eps)
990 dem += SECONDARY_BEAM_DEMERIT;
992 if (rdir == DOWN && dy <= eps
993 && fabs (my_modf (yr) - straddle) < eps)
994 dem += SECONDARY_BEAM_DEMERIT;
1004 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
1006 Beam::least_squares (SCM smob)
1008 Grob *me = unsmob_grob (smob);
1010 int count = visible_stem_count (me);
1011 Interval pos (0, 0);
1015 me->set_grob_property ("positions", ly_interval2scm (pos));
1016 return SCM_UNSPECIFIED;
1020 Array<Real> x_posns ;
1021 Link_array<Grob> stems=
1022 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1023 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
1024 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
1026 Real my_y = me->relative_coordinate (commony, Y_AXIS);
1028 Grob *fvs = first_visible_stem (me);
1029 Grob *lvs = last_visible_stem (me);
1031 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
1032 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
1033 Stem::calc_stem_info (lvs).ideal_y_
1034 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
1036 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1037 for (int i=0; i < stems.size (); i++)
1041 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
1044 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
1050 if (!ideal.delta ())
1052 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
1053 Stem::chord_start_y (last_visible_stem (me)));
1057 TODO -- use scoring for this.
1059 complicated, because we take stem-info.ideal for determining
1062 /* Make simple beam on middle line have small tilt */
1063 if (!ideal[LEFT] && chord.delta () && count == 2)
1069 Direction d = (Direction) (sign (chord.delta ()) * UP);
1070 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
1085 Array<Offset> ideals;
1086 for (int i=0; i < stems.size (); i++)
1089 if (Stem::invisible_b (s))
1091 ideals.push (Offset (x_posns[i],
1092 Stem::calc_stem_info (s).ideal_y_
1093 + s->relative_coordinate (commony, Y_AXIS)
1096 minimise_least_squares (&dydx, &y, ideals);
1099 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
1100 pos = Interval (y, (y+dy));
1103 me->set_grob_property ("positions", ly_interval2scm (pos));
1105 return SCM_UNSPECIFIED;
1110 We can't combine with previous function, since check concave and
1111 slope damping comes first.
1113 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
1115 Beam::shift_region_to_valid (SCM grob)
1117 Grob *me = unsmob_grob (grob);
1121 Array<Real> x_posns ;
1122 Link_array<Grob> stems=
1123 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1124 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
1125 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
1127 Grob *fvs = first_visible_stem (me);
1130 return SCM_UNSPECIFIED;
1132 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
1133 for (int i=0; i < stems.size (); i++)
1137 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
1141 Grob *lvs = last_visible_stem (me);
1143 return SCM_UNSPECIFIED;
1145 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
1147 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
1148 Real dy = pos.delta();
1154 Shift the positions so that we have a chance of finding good
1155 quants (i.e. no short stem failures.)
1157 Interval feasible_left_point;
1158 feasible_left_point.set_full ();
1159 for (int i=0; i < stems.size (); i++)
1162 if (Stem::invisible_b (s))
1165 Direction d = Stem::get_direction (s);
1168 Stem::calc_stem_info (s).shortest_y_
1169 - dydx * x_posns [i];
1172 left_y is now relative to the stem S. We want relative to
1173 ourselves, so translate:
1176 + s->relative_coordinate (commony, Y_AXIS)
1177 - me->relative_coordinate (commony, Y_AXIS);
1183 feasible_left_point.intersect (flp);
1186 if (feasible_left_point.empty_b())
1188 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1190 else if (!feasible_left_point.elem_b(y))
1192 if (isinf (feasible_left_point[DOWN]))
1193 y = feasible_left_point[UP] - REGION_SIZE;
1194 else if (isinf (feasible_left_point[UP]))
1195 y = feasible_left_point[DOWN]+ REGION_SIZE;
1197 y = feasible_left_point.center ();
1199 pos = Interval (y, (y+dy));
1200 me->set_grob_property ("positions", ly_interval2scm (pos));
1201 return SCM_UNSPECIFIED;
1205 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1207 Beam::check_concave (SCM smob)
1209 Grob *me = unsmob_grob (smob);
1211 Link_array<Grob> stems =
1212 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1214 for (int i = 0; i < stems.size ();)
1216 if (Stem::invisible_b (stems[i]))
1222 if (stems.size () < 3)
1223 return SCM_UNSPECIFIED;
1226 /* Concaveness #1: If distance of an inner notehead to line between
1227 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1228 beam is concave (Heinz Stolba).
1230 In the case of knees, the line connecting outer heads is often
1231 not related to the beam slope (it may even go in the other
1232 direction). Skip the check when the outer stems point in
1233 different directions. --hwn
1236 bool concaveness1 = false;
1237 SCM gap = me->get_grob_property ("concaveness-gap");
1238 if (gh_number_p (gap)
1239 && Stem::get_direction(stems.top ())
1240 == Stem::get_direction(stems[0]))
1242 Real r1 = gh_scm2double (gap);
1243 Real dy = Stem::chord_start_y (stems.top ())
1244 - Stem::chord_start_y (stems[0]);
1247 Real slope = dy / (stems.size () - 1);
1249 Real y0 = Stem::chord_start_y (stems[0]);
1250 for (int i = 1; i < stems.size () - 1; i++)
1252 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1255 concaveness1 = true;
1262 /* Concaveness #2: Sum distances of inner noteheads that fall
1263 outside the interval of the two outer noteheads.
1265 We only do this for beams where first and last stem have the same
1269 Note that "convex" stems compensate for "concave" stems.
1270 (is that intentional?) --hwn.
1273 Real concaveness2 = 0;
1274 SCM thresh = me->get_grob_property ("concaveness-threshold");
1275 Real r2 = infinity_f;
1276 if (!concaveness1 && gh_number_p (thresh)
1277 && Stem::get_direction(stems.top ())
1278 == Stem::get_direction(stems[0]))
1280 r2 = gh_scm2double (thresh);
1282 Direction dir = Stem::get_direction(stems.top ());
1284 Interval iv (Stem::chord_start_y (stems[0]),
1285 Stem::chord_start_y (stems.top ()));
1287 if (iv[MAX] < iv[MIN])
1290 for (int i = 1; i < stems.size () - 1; i++)
1292 Real f = Stem::chord_start_y (stems[i]);
1293 concave += ((f - iv[MAX] ) >? 0) +
1294 ((f - iv[MIN] ) <? 0);
1297 concaveness2 = concave / (stems.size () - 2);
1299 /* ugh: this is the a kludge to get
1300 input/regression/beam-concave.ly to behave as
1304 huh? we're dividing twice (which is not scalable) meaning that
1305 the longer the beam, the more unlikely it will be
1306 concave. Maybe you would even expect the other way around??
1311 concaveness2 /= (stems.size () - 2);
1314 /* TODO: some sort of damping iso -> plain horizontal */
1315 if (concaveness1 || concaveness2 > r2)
1317 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1318 Real r = pos.linear_combination (0);
1319 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1320 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1323 return SCM_UNSPECIFIED;
1326 /* This neat trick is by Werner Lemberg,
1327 damped = tanh (slope)
1328 corresponds with some tables in [Wanske] CHECKME */
1329 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1331 Beam::slope_damping (SCM smob)
1333 Grob *me = unsmob_grob (smob);
1335 if (visible_stem_count (me) <= 1)
1336 return SCM_UNSPECIFIED;
1338 SCM s = me->get_grob_property ("damping");
1339 int damping = gh_scm2int (s);
1343 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1344 Real dy = pos.delta ();
1346 Grob *fvs = first_visible_stem (me);
1347 Grob *lvs = last_visible_stem (me);
1349 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1352 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1353 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1354 Real dydx = dy && dx ? dy/dx : 0;
1355 dydx = 0.6 * tanh (dydx) / damping;
1357 Real damped_dy = dydx * dx;
1358 pos[LEFT] += (dy - damped_dy) / 2;
1359 pos[RIGHT] -= (dy - damped_dy) / 2;
1361 me->set_grob_property ("positions", ly_interval2scm (pos));
1363 return SCM_UNSPECIFIED;
1367 where_are_the_whole_beams(SCM beaming)
1371 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1373 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1375 l.add_point (gh_scm2int (gh_car (s)));
1382 Calculate the Y position of the stem-end, given the Y-left, Y-right
1383 in POS, and for stem S.
1386 Beam::calc_stem_y (Grob *me, Grob* s, Grob * common_y, Interval pos, bool french)
1388 Real beam_space = get_beam_space (me);
1390 // ugh -> use commonx
1391 Grob * fvs = first_visible_stem (me);
1392 Grob *lvs = last_visible_stem (me);
1394 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1395 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1396 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1397 Real dy = pos.delta ();
1398 Real stem_y_beam0 = (dy && dx
1405 Direction my_dir = Directional_element_interface::get (s);
1406 SCM beaming = s->get_grob_property ("beaming");
1408 Real stem_y = stem_y_beam0;
1411 Slice bm = where_are_the_whole_beams (beaming);
1413 stem_y += beam_space * bm[-my_dir];
1417 Slice bm = Stem::beam_multiplicity(s);
1419 stem_y +=bm[my_dir] * beam_space;
1422 Real id = me->relative_coordinate (common_y, Y_AXIS)
1423 - s->relative_coordinate (common_y, Y_AXIS);
1429 Hmm. At this time, beam position and slope are determined. Maybe,
1430 stem directions and length should set to relative to the chord's
1431 position of the beam. */
1433 Beam::set_stem_lengths (Grob *me)
1435 Link_array<Grob> stems=
1436 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1438 if (stems.size () <= 1)
1441 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
1442 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1443 Real staff_space = Staff_symbol_referencer::staff_space (me);
1445 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1447 for (int i=0; i < stems.size (); i++)
1450 if (Stem::invisible_b (s))
1453 Real stem_y = calc_stem_y (me, s, common , pos, french && i > 0&& (i < stems.size () -1));
1455 Stem::set_stemend (s, 2* stem_y / staff_space);
1460 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1462 Link_array<Grob> stems=
1463 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1466 for (int i=0; i < stems.size (); i++)
1469 Don't overwrite user settings.
1474 /* Don't set beaming for outside of outer stems */
1475 if ((d == LEFT && i == 0)
1476 ||(d == RIGHT && i == stems.size () -1))
1480 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1481 if (beaming_prop == SCM_EOL ||
1482 index_get_cell (beaming_prop, d) == SCM_EOL)
1484 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1485 Stem::set_beaming (stems[i], b, d);
1488 while (flip (&d) != LEFT);
1493 Beam::forced_stem_count (Grob *me)
1495 Link_array<Grob>stems =
1496 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1498 for (int i=0; i < stems.size (); i++)
1502 if (Stem::invisible_b (s))
1505 if (((int)Stem::chord_start_y (s))
1506 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1516 Beam::visible_stem_count (Grob *me)
1518 Link_array<Grob>stems =
1519 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1521 for (int i = stems.size (); i--;)
1523 if (!Stem::invisible_b (stems[i]))
1530 Beam::first_visible_stem (Grob *me)
1532 Link_array<Grob>stems =
1533 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1535 for (int i = 0; i < stems.size (); i++)
1537 if (!Stem::invisible_b (stems[i]))
1544 Beam::last_visible_stem (Grob *me)
1546 Link_array<Grob>stems =
1547 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1548 for (int i = stems.size (); i--;)
1550 if (!Stem::invisible_b (stems[i]))
1560 handle rest under beam (do_post: beams are calculated now)
1561 what about combination of collisions and rest under beam.
1565 rest -> stem -> beam -> interpolate_y_position ()
1567 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1569 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1571 Grob *rest = unsmob_grob (element_smob);
1572 Axis a = (Axis) gh_scm2int (axis);
1574 assert (a == Y_AXIS);
1576 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1579 return gh_double2scm (0.0);
1580 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1582 || !Beam::has_interface (beam)
1583 || !Beam::visible_stem_count (beam))
1584 return gh_double2scm (0.0);
1586 // make callback for rest from this.
1587 // todo: make sure this calced already.
1589 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1590 Interval pos (0, 0);
1591 SCM s = beam->get_grob_property ("positions");
1592 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1593 pos = ly_scm2interval (s);
1595 Real dy = pos.delta ();
1596 // ugh -> use commonx
1597 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1598 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1599 Real dydx = dy && dx ? dy/dx : 0;
1601 Direction d = Stem::get_direction (stem);
1602 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1604 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1607 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1610 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1612 minimum_dist + -d * (beamy - rest_dim) >? 0;
1614 int stafflines = Staff_symbol_referencer::line_count (rest);
1616 // move discretely by half spaces.
1617 int discrete_dist = int (ceil (dist));
1619 // move by whole spaces inside the staff.
1620 if (discrete_dist < stafflines+1)
1621 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1623 return gh_double2scm (-d * discrete_dist);
1629 ADD_INTERFACE (Beam, "beam-interface",
1632 #'thickness= weight of beams, in staffspace
1635 We take the least squares line through the ideal-length stems, and
1636 then damp that using
1638 damped = tanh (slope)
1640 this gives an unquantized left and right position for the beam end.
1641 Then we take all combinations of quantings near these left and right
1642 positions, and give them a score (according to how close they are to
1643 the ideal slope, how close the result is to the ideal stems, etc.). We
1644 take the best scoring combination.
1647 "french-beaming position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy damping flag-width-function neutral-direction positions space-function thickness");