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>
15 * Use Number_pair i.s.o Interval to represent (yl, yr).
19 - Stems run to the Y-center of the beam.
21 - beam_space is the offset between Y centers of the beam.
26 #include <math.h> // tanh.
28 #include "molecule.hh"
29 #include "directional-element-interface.hh"
33 #include "least-squares.hh"
35 #include "paper-def.hh"
37 #include "group-interface.hh"
38 #include "staff-symbol-referencer.hh"
44 #define DEBUG_QUANTING 0
48 #include "text-item.hh" // debug output.
49 #include "font-interface.hh" // debug output.
53 const int INTER_QUANT_PENALTY = 1000;
54 const int SECONDARY_BEAM_DEMERIT = 15;
55 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
56 // possibly ridiculous, but too short stems just won't do
57 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
58 const int DAMPING_DIRECTIION_PENALTY = 800;
59 const int MUSICAL_DIRECTION_FACTOR = 400;
60 const int IDEAL_SLOPE_FACTOR = 10;
61 const int REGION_SIZE = 2;
65 shrink_extra_weight (Real x)
67 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
72 int_list_to_slice (SCM l)
76 for (; gh_pair_p (l); l = gh_cdr (l))
78 if (gh_number_p (gh_car (l)))
79 s.add_point (gh_scm2int (gh_car (l)));
87 stem_beam_multiplicity (Grob *stem)
89 SCM beaming= stem->get_grob_property ("beaming");
90 Slice l = int_list_to_slice (gh_car (beaming));
91 Slice r = int_list_to_slice (gh_cdr (beaming));
98 Beam::add_stem (Grob *me, Grob *s)
100 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
102 s->add_dependency (me);
104 assert (!Stem::beam_l (s));
105 s->set_grob_property ("beam", me->self_scm ());
107 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
111 Beam::get_beam_space (Grob *me)
113 SCM func = me->get_grob_property ("space-function");
114 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
115 return gh_scm2double (s);
122 Beam::get_beam_count (Grob *me)
125 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
127 Grob *sc = unsmob_grob (ly_car (s));
129 m = m >? (stem_beam_multiplicity (sc).length () + 1);
134 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
136 Beam::space_function (SCM smob, SCM beam_count)
138 Grob *me = unsmob_grob (smob);
140 Real staff_space = Staff_symbol_referencer::staff_space (me);
141 Real line = me->paper_l ()->get_var ("linethickness");
142 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
145 Real beam_space = gh_scm2int (beam_count) < 4
146 ? (2*staff_space + line - thickness) / 2.0
147 : (3*staff_space + line - thickness) / 3.0;
149 return gh_double2scm (beam_space);
153 /* After pre-processing all directions should be set.
154 Several post-processing routines (stem, slur, script) need stem/beam
156 Currenly, this means that beam has set all stem's directions.
157 [Alternatively, stems could set its own directions, according to
158 their beam, during 'final-pre-processing'.] */
159 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
161 Beam::before_line_breaking (SCM smob)
163 Grob *me = unsmob_grob (smob);
165 /* Beams with less than 2 two stems don't make much sense, but could happen
170 For a beam that only has one stem, we try to do some disappearance magic:
171 we revert the flag, and move on to The Eternal Engraving Fields. */
173 int count = visible_stem_count (me);
176 me->warning (_ ("beam has less than two visible stems"));
178 SCM stems = me->get_grob_property ("stems");
179 if (scm_ilength (stems) == 1)
181 me->warning (_ ("Beam has less than two stems. Removing beam."));
183 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
186 return SCM_UNSPECIFIED;
188 else if (scm_ilength (stems) == 0)
191 return SCM_UNSPECIFIED;
196 Direction d = get_default_dir (me);
198 consider_auto_knees (me, d);
199 set_stem_directions (me, d);
203 set_stem_shorten (me);
212 Beam::connect_beams (Grob *me)
214 Link_array<Grob> stems=
215 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
218 last_int.set_empty();
219 for (int i = 0; i< stems.size(); i++)
221 Grob *this_stem = stems[i];
222 SCM this_beaming = this_stem->get_grob_property ("beaming");
224 Direction this_dir = Directional_element_interface::get(this_stem);
227 int start_point = last_int [this_dir];
233 if (d == RIGHT && i == stems.size()-1)
236 new_slice.set_empty();
237 SCM s = index_get_cell (this_beaming, d);
238 for (; gh_pair_p (s); s = gh_cdr (s))
241 start_point - this_dir * gh_scm2int (gh_car (s));
243 new_slice.add_point (new_beam_pos);
244 gh_set_car_x (s, gh_int2scm (new_beam_pos));
247 while (flip (&d) != LEFT);
249 last_int = new_slice;
253 SCM s = gh_cdr (this_beaming);
254 for (; gh_pair_p (s); s = gh_cdr (s))
256 int np = - this_dir * gh_scm2int (gh_car(s));
257 gh_set_car_x (s, gh_int2scm (np));
258 last_int.add_point (np);
264 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
266 Beam::brew_molecule (SCM grob)
268 Grob *me = unsmob_grob (grob);
269 Link_array<Grob> stems=
270 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
271 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
274 if (visible_stem_count (me))
276 // ugh -> use commonx
277 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
278 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
282 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
283 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
286 SCM posns = me->get_grob_property ("positions");
288 if (!ly_number_pair_p (posns))
290 programming_error ("No beam posns");
291 pos = Interval (0,0);
294 pos= ly_scm2interval (posns);
296 Real dy = pos.delta ();
297 Real dydx = dy && dx ? dy/dx : 0;
299 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
300 Real bdy = get_beam_space (me);
302 SCM last_beaming = SCM_EOL;;
303 Real last_xposn = -1;
304 Real last_width = -1 ;
307 * Determine auto knees based on positions if it's set by the user.
310 Real lt = me->paper_l ()->get_var ("linethickness");
311 for (int i = 0; i< stems.size(); i++)
315 SCM this_beaming = st->get_grob_property ("beaming");
316 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
317 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
321 SCM left = gh_cdr (last_beaming);
322 SCM right = gh_car (this_beaming);
324 Array<int> fullbeams;
325 Array<int> lfliebertjes;
326 Array<int> rfliebertjes;
329 gh_pair_p (s); s =gh_cdr (s))
331 int b = gh_scm2int (gh_car (s));
332 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
338 lfliebertjes.push (b);
342 gh_pair_p (s); s =gh_cdr (s))
344 int b = gh_scm2int (gh_car (s));
345 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
347 rfliebertjes.push (b);
352 Real w = xposn - last_xposn;
353 Real stem_offset = 0.0;
354 Real width_corr = 0.0;
357 stem_offset -= last_width/2;
358 width_corr += last_width/2;
361 if (i == stems.size() -1)
363 width_corr += stem_width/2;
366 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
367 for (int j = fullbeams.size(); j--;)
370 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
371 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
372 the_beam.add_molecule (b);
375 if (lfliebertjes.size() || rfliebertjes.size())
379 if (!Stem::first_head (st))
383 int t = Stem::duration_log (st);
385 SCM proc = me->get_grob_property ("flag-width-function");
386 SCM result = gh_call1 (proc, gh_int2scm (t));
387 nw_f = gh_scm2double (result);
390 /* Half beam should be one note-width,
391 but let's make sure two half-beams never touch */
393 Real w = xposn - last_xposn;
396 Molecule half = Lookup::beam (dydx, w, thick);
397 for (int j = lfliebertjes.size(); j--;)
400 b.translate_axis (last_xposn - x0, X_AXIS);
401 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
402 the_beam.add_molecule (b);
404 for (int j = rfliebertjes.size(); j--;)
407 b.translate_axis (xposn - x0 - w , X_AXIS);
408 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
409 the_beam.add_molecule (b);
415 last_width = stem_width;
416 last_beaming = this_beaming;
419 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
420 the_beam.translate_axis (pos[LEFT], Y_AXIS);
425 This code prints the demerits for each beam. Perhaps this
426 should be switchable for those who want to twiddle with the
432 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
435 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
438 SCM properties = Font_interface::font_alist_chain (me);
441 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
442 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
448 return the_beam.smobbed_copy();
455 Beam::get_default_dir (Grob *me)
457 Drul_array<int> total;
458 total[UP] = total[DOWN] = 0;
459 Drul_array<int> count;
460 count[UP] = count[DOWN] = 0;
463 Link_array<Item> stems=
464 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
466 for (int i=0; i <stems.size (); i++)
469 Direction sd = Directional_element_interface::get (s);
471 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
472 int current = sd ? (1 + d * sd)/2 : center_distance;
479 } while (flip (&d) != DOWN);
481 SCM func = me->get_grob_property ("dir-function");
482 SCM s = gh_call2 (func,
483 gh_cons (gh_int2scm (count[UP]),
484 gh_int2scm (count[DOWN])),
485 gh_cons (gh_int2scm (total[UP]),
486 gh_int2scm (total[DOWN])));
488 if (gh_number_p (s) && gh_scm2int (s))
491 /* If dir is not determined: get default */
492 return to_dir (me->get_grob_property ("neutral-direction"));
496 /* Set all stems with non-forced direction to beam direction.
497 Urg: non-forced should become `without/with unforced' direction,
498 once stem gets cleaned-up. */
500 Beam::set_stem_directions (Grob *me, Direction d)
502 Link_array<Item> stems
503 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
505 for (int i=0; i <stems.size (); i++)
508 SCM force = s->remove_grob_property ("dir-forced");
509 if (!gh_boolean_p (force) || !gh_scm2bool (force))
510 Directional_element_interface::set (s, d);
514 /* Simplistic auto-knees; only consider vertical gap between two
517 `Forced' stem directions are ignored. If you don't want auto-knees,
518 don't set, or unset auto-knee-gap. */
520 Beam::consider_auto_knees (Grob *me, Direction d)
522 SCM scm = me->get_grob_property ("auto-knee-gap");
524 if (gh_number_p (scm))
528 Real staff_space = Staff_symbol_referencer::staff_space (me);
529 Real gap = gh_scm2double (scm) / staff_space;
532 Link_array<Item> stems=
533 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
535 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
536 for (int i=1; i < stems.size (); i++)
537 if (!Stem::invisible_b (stems[i]))
538 common = common->common_refpoint (stems[i], Y_AXIS);
541 for (int i=1; i < stems.size (); i++)
543 if (!Stem::invisible_b (stems[i-1]))
545 if (Stem::invisible_b (stems[l]))
547 if (Stem::invisible_b (stems[i]))
550 Real left = Stem::extremal_heads (stems[l])[d]
551 ->relative_coordinate (common, Y_AXIS);
552 Real right = Stem::extremal_heads (stems[i])[-d]
553 ->relative_coordinate (common, Y_AXIS);
555 Real dy = right - left;
559 knee_y = (right + left) / 2;
567 for (int i=0; i < stems.size (); i++)
570 if (Stem::invisible_b (s) ||
571 s->get_grob_property ("dir-forced") == SCM_BOOL_T)
573 Real y = Stem::extremal_heads (stems[i])[d]
574 ->relative_coordinate (common, Y_AXIS);
576 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
577 s->set_grob_property ("dir-forced", SCM_BOOL_T);
583 /* Set stem's shorten property if unset.
586 take some y-position (chord/beam/nearest?) into account
587 scmify forced-fraction
591 why is shorten stored in beam, and not directly in stem?
595 Beam::set_stem_shorten (Grob *m)
597 Spanner*me = dynamic_cast<Spanner*> (m);
599 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
601 int beam_count = get_beam_count (me);
603 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
604 if (shorten == SCM_EOL)
607 int sz = scm_ilength (shorten);
609 Real staff_space = Staff_symbol_referencer::staff_space (me);
610 SCM shorten_elt = scm_list_ref (shorten,
611 gh_int2scm (beam_count <? (sz - 1)));
612 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
614 /* your similar cute comment here */
615 shorten_f *= forced_fraction;
618 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
621 /* Call list of y-dy-callbacks, that handle setting of
625 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
627 Beam::after_line_breaking (SCM smob)
629 Grob *me = unsmob_grob (smob);
631 /* Copy to mutable list. */
632 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
633 me->set_grob_property ("positions", s);
635 if (ly_car (s) == SCM_BOOL_F)
638 // one wonders if such genericity is necessary --hwn.
639 SCM callbacks = me->get_grob_property ("position-callbacks");
640 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
641 gh_call1 (ly_car (i), smob);
644 set_stem_lengths (me);
645 return SCM_UNSPECIFIED;
659 - Make all demerits customisable
661 - One sensible check per demerit (what's this --hwn)
663 - Add demerits for quants per se, as to forbid a specific quant
667 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
669 Beam::quanting (SCM smob)
671 Grob *me = unsmob_grob (smob);
673 SCM s = me->get_grob_property ("positions");
674 Real yl = gh_scm2double (gh_car (s));
675 Real yr = gh_scm2double (gh_cdr (s));
677 Real ss = Staff_symbol_referencer::staff_space (me);
678 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
679 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
682 SCM sdy = me->get_grob_property ("least-squares-dy");
683 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
686 Real sit = (thickness - slt) / 2;
688 Real hang = 1.0 - (thickness - slt) / 2;
689 Real quants [] = {straddle, sit, inter, hang };
691 int num_quants = int (sizeof (quants)/sizeof (Real));
696 going to REGION_SIZE == 2, yields another 0.6 second with
700 (result indexes between 70 and 575) ? --hwn.
707 Do stem computations. These depend on YL and YR linearly, so we can
708 precompute for every stem 2 factors.
710 Link_array<Grob> stems=
711 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
712 Array<Stem_info> stem_infos;
713 Array<Real> lbase_lengths;
714 Array<Real> rbase_lengths;
716 Drul_array<bool> dirs_found(0,0);
718 bool french = to_boolean (me->get_grob_property ("french-beaming"));
719 for (int i= 0; i < stems.size(); i++)
722 stem_infos.push (Stem::calc_stem_info (s));
723 dirs_found[stem_infos.top ().dir_] = true;
725 Real b = calc_stem_y (me, s, Interval (1,0), french && i > 0&& (i < stems.size () -1));
726 lbase_lengths.push (b);
728 Real a = calc_stem_y (me, s, Interval (0,1), french && i > 0&& (i < stems.size () -1));
729 rbase_lengths.push (a);
732 Direction ldir = Direction (stem_infos[0].dir_);
733 Direction rdir = Direction (stem_infos.top ().dir_);
734 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
737 int region_size = REGION_SIZE;
739 Knees are harder, lets try some more possibilities for knees.
744 for (int i = -region_size ; i < region_size; i++)
745 for (int j = 0; j < num_quants; j++)
747 quantsl.push (i + quants[j] + int (yl));
748 quantsr.push (i + quants[j] + int (yr));
751 Array<Quant_score> qscores;
753 for (int l =0; l < quantsl.size (); l++)
754 for (int r =0; r < quantsr.size (); r++)
766 This is a longish function, but we don't separate this out into
767 neat modular separate subfunctions, as the subfunctions would be
768 called for many values of YL, YR. By precomputing various
769 parameters outside of the loop, we can save a lot of time.
772 for (int i = qscores.size (); i--;)
773 if (qscores[i].demerits < 100)
776 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
780 Real rad = Staff_symbol_referencer::staff_radius (me);
781 int beam_count = get_beam_count (me);
782 Real beam_space = beam_count < 4
783 ? (2*ss + slt - thickness) / 2.0
784 : (3*ss + slt - thickness) / 3.0;
786 for (int i = qscores.size (); i--;)
787 if (qscores[i].demerits < 100)
790 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
791 rad, slt, thickness, beam_space,
792 beam_count, ldir, rdir);
796 for (int i = qscores.size (); i--;)
797 if (qscores[i].demerits < 100)
800 += score_stem_lengths (stems, stem_infos,
801 lbase_lengths, rbase_lengths,
803 me, qscores[i].yl, qscores[i].yr);
809 for (int i = qscores.size (); i--;)
811 if (qscores[i].demerits < best)
813 best = qscores [i].demerits ;
819 me->set_grob_property ("positions",
820 gh_cons (gh_double2scm (qscores[best_idx].yl),
821 gh_double2scm (qscores[best_idx].yr))
827 me->set_grob_property ("quant-score",
828 gh_double2scm (qscores[best_idx].demerits));
829 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
832 return SCM_UNSPECIFIED;
836 Beam::score_stem_lengths (Link_array<Grob>stems,
837 Array<Stem_info> stem_infos,
838 Array<Real> left_factor,
839 Array<Real> right_factor,
844 Real demerit_score = 0.0 ;
845 Real pen = STEM_LENGTH_LIMIT_PENALTY;
847 for (int i=0; i < stems.size (); i++)
850 if (Stem::invisible_b (s))
854 yl * left_factor[i] + right_factor[i]* yr;
856 Stem_info info = stem_infos[i];
857 Direction d = info.dir_;
860 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
862 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
863 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
866 demerit_score *= 2.0 / stems.size ();
868 return demerit_score;
872 Beam::score_slopes_dy (Grob *me,
874 Real dy_mus, Real dy_damp)
879 if (sign (dy_damp) != sign (dy))
881 dem += DAMPING_DIRECTIION_PENALTY;
884 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
885 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
893 return x - floor (x);
897 Beam::score_forbidden_quants (Grob*me,
901 Real thickness, Real beam_space,
903 Direction ldir, Direction rdir)
908 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
909 dem += INTER_QUANT_PENALTY;
910 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
911 dem += INTER_QUANT_PENALTY;
913 // todo: use beam_count of outer stems.
918 Real sit = (thickness - slt) / 2;
920 Real hang = 1.0 - (thickness - slt) / 2;
923 if (fabs (yl - ldir * beam_space) < rad
924 && fabs (my_modf (yl) - inter) < 1e-3)
925 dem += SECONDARY_BEAM_DEMERIT;
926 if (fabs (yr - rdir * beam_space) < rad
927 && fabs (my_modf (yr) - inter) < 1e-3)
928 dem += SECONDARY_BEAM_DEMERIT;
933 Can't we simply compute the distance between the nearest
934 staffline and the secondary beam? That would get rid of the
935 silly case analysis here (which is probably not when we have
936 different beam-thicknesses.)
942 // hmm, without Interval/Drul_array, you get ~ 4x same code...
943 if (fabs (yl - ldir * beam_space) < rad + inter)
945 if (ldir == UP && dy <= eps
946 && fabs (my_modf (yl) - sit) < eps)
947 dem += SECONDARY_BEAM_DEMERIT;
949 if (ldir == DOWN && dy >= eps
950 && fabs (my_modf (yl) - hang) < eps)
951 dem += SECONDARY_BEAM_DEMERIT;
954 if (fabs (yr - rdir * beam_space) < rad + inter)
956 if (rdir == UP && dy >= eps
957 && fabs (my_modf (yr) - sit) < eps)
958 dem += SECONDARY_BEAM_DEMERIT;
960 if (rdir == DOWN && dy <= eps
961 && fabs (my_modf (yr) - hang) < eps)
962 dem += SECONDARY_BEAM_DEMERIT;
967 if (fabs (yl - 2 * ldir * beam_space) < rad + inter)
969 if (ldir == UP && dy <= eps
970 && fabs (my_modf (yl) - straddle) < eps)
971 dem += SECONDARY_BEAM_DEMERIT;
973 if (ldir == DOWN && dy >= eps
974 && fabs (my_modf (yl) - straddle) < eps)
975 dem += SECONDARY_BEAM_DEMERIT;
978 if (fabs (yr - 2 * rdir * beam_space) < rad + inter)
980 if (rdir == UP && dy >= eps
981 && fabs (my_modf (yr) - straddle) < eps)
982 dem += SECONDARY_BEAM_DEMERIT;
984 if (rdir == DOWN && dy <= eps
985 && fabs (my_modf (yr) - straddle) < eps)
986 dem += SECONDARY_BEAM_DEMERIT;
996 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
998 Beam::least_squares (SCM smob)
1000 Grob *me = unsmob_grob (smob);
1002 int count = visible_stem_count (me);
1003 Interval pos (0, 0);
1007 me->set_grob_property ("positions", ly_interval2scm (pos));
1008 return SCM_UNSPECIFIED;
1011 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
1012 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
1016 Array<Real> x_posns ;
1017 Link_array<Item> stems=
1018 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1019 Grob *common = stems[0];
1020 for (int i=1; i < stems.size (); i++)
1021 common = stems[i]->common_refpoint (common, X_AXIS);
1023 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
1024 for (int i=0; i < stems.size (); i++)
1028 Real x = s->relative_coordinate (common, X_AXIS) - x0;
1031 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
1037 if (!ideal.delta ())
1039 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
1040 Stem::chord_start_y (last_visible_stem (me)));
1044 TODO -- use scoring for this.
1046 complicated, because we take stem-info.ideal for determining
1049 /* Make simple beam on middle line have small tilt */
1050 if (!ideal[LEFT] && chord.delta () && count == 2)
1056 Direction d = (Direction) (sign (chord.delta ()) * UP);
1057 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
1072 Array<Offset> ideals;
1073 for (int i=0; i < stems.size (); i++)
1076 if (Stem::invisible_b (s))
1078 ideals.push (Offset (x_posns[i],
1079 Stem::calc_stem_info (s).ideal_y_));
1081 minimise_least_squares (&dydx, &y, ideals);
1084 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
1085 pos = Interval (y, (y+dy));
1088 me->set_grob_property ("positions", ly_interval2scm (pos));
1090 return SCM_UNSPECIFIED;
1095 We can't combine with previous function, since check concave and
1096 slope damping comes first.
1098 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
1100 Beam::shift_region_to_valid (SCM grob)
1102 Grob *me = unsmob_grob (grob);
1106 Array<Real> x_posns ;
1107 Link_array<Item> stems=
1108 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1109 Grob *common = stems[0];
1110 for (int i=1; i < stems.size (); i++)
1111 common = stems[i]->common_refpoint (common, X_AXIS);
1113 Grob *fvs = first_visible_stem (me);
1116 return SCM_UNSPECIFIED;
1118 Real x0 =fvs->relative_coordinate (common, X_AXIS);
1119 for (int i=0; i < stems.size (); i++)
1123 Real x = s->relative_coordinate (common, X_AXIS) - x0;
1127 Grob *lvs = last_visible_stem (me);
1129 return SCM_UNSPECIFIED;
1131 Real dx = lvs->relative_coordinate (common, X_AXIS) - x0;
1133 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
1134 Real dy = pos.delta();
1140 Shift the positions so that we have a chance of finding good
1141 quants (i.e. no short stem failures.)
1143 Interval feasible_left_point;
1144 feasible_left_point.set_full ();
1145 for (int i=0; i < stems.size (); i++)
1148 if (Stem::invisible_b (s))
1152 Direction d = Stem::get_direction (s);
1155 Real left_y = Stem::calc_stem_info (s).shortest_y_
1156 - dydx * x_posns [i];
1162 feasible_left_point.intersect (flp);
1165 if (feasible_left_point.empty_b())
1167 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1169 else if (!feasible_left_point.elem_b(y))
1171 if (isinf (feasible_left_point[DOWN]))
1172 y = feasible_left_point[UP] - REGION_SIZE;
1173 else if (isinf (feasible_left_point[UP]))
1174 y = feasible_left_point[DOWN]+ REGION_SIZE;
1176 y = feasible_left_point.center ();
1178 pos = Interval (y, (y+dy));
1179 me->set_grob_property ("positions", ly_interval2scm (pos));
1180 return SCM_UNSPECIFIED;
1184 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1186 Beam::check_concave (SCM smob)
1188 Grob *me = unsmob_grob (smob);
1190 Link_array<Item> stems =
1191 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1193 for (int i = 0; i < stems.size ();)
1195 if (Stem::invisible_b (stems[i]))
1201 if (stems.size () < 3)
1202 return SCM_UNSPECIFIED;
1205 /* Concaveness #1: If distance of an inner notehead to line between
1206 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1207 beam is concave (Heinz Stolba).
1209 In the case of knees, the line connecting outer heads is often
1210 not related to the beam slope (it may even go in the other
1211 direction). Skip the check when the outer stems point in
1212 different directions. --hwn
1215 bool concaveness1 = false;
1216 SCM gap = me->get_grob_property ("concaveness-gap");
1217 if (gh_number_p (gap)
1218 && Stem::get_direction(stems.top ())
1219 == Stem::get_direction(stems[0]))
1221 Real r1 = gh_scm2double (gap);
1222 Real dy = Stem::chord_start_y (stems.top ())
1223 - Stem::chord_start_y (stems[0]);
1226 Real slope = dy / (stems.size () - 1);
1228 Real y0 = Stem::chord_start_y (stems[0]);
1229 for (int i = 1; i < stems.size () - 1; i++)
1231 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1234 concaveness1 = true;
1241 /* Concaveness #2: Sum distances of inner noteheads that fall
1242 outside the interval of the two outer noteheads.
1244 We only do this for beams where first and last stem have the same
1248 Note that "convex" stems compensate for "concave" stems.
1249 (is that intentional?) --hwn.
1252 Real concaveness2 = 0;
1253 SCM thresh = me->get_grob_property ("concaveness-threshold");
1254 Real r2 = infinity_f;
1255 if (!concaveness1 && gh_number_p (thresh)
1256 && Stem::get_direction(stems.top ())
1257 == Stem::get_direction(stems[0]))
1259 r2 = gh_scm2double (thresh);
1261 Direction dir = Stem::get_direction(stems.top ());
1263 Interval iv (Stem::chord_start_y (stems[0]),
1264 Stem::chord_start_y (stems.top ()));
1266 if (iv[MAX] < iv[MIN])
1269 for (int i = 1; i < stems.size () - 1; i++)
1271 Real f = Stem::chord_start_y (stems[i]);
1272 concave += ((f - iv[MAX] ) >? 0) +
1273 ((f - iv[MIN] ) <? 0);
1276 concaveness2 = concave / (stems.size () - 2);
1278 /* ugh: this is the a kludge to get
1279 input/regression/beam-concave.ly to behave as
1283 huh? we're dividing twice (which is not scalable) meaning that
1284 the longer the beam, the more unlikely it will be
1285 concave. Maybe you would even expect the other way around??
1290 concaveness2 /= (stems.size () - 2);
1293 /* TODO: some sort of damping iso -> plain horizontal */
1294 if (concaveness1 || concaveness2 > r2)
1296 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1297 Real r = pos.linear_combination (0);
1298 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1299 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1302 return SCM_UNSPECIFIED;
1305 /* This neat trick is by Werner Lemberg,
1306 damped = tanh (slope)
1307 corresponds with some tables in [Wanske] CHECKME */
1308 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1310 Beam::slope_damping (SCM smob)
1312 Grob *me = unsmob_grob (smob);
1314 if (visible_stem_count (me) <= 1)
1315 return SCM_UNSPECIFIED;
1317 SCM s = me->get_grob_property ("damping");
1318 int damping = gh_scm2int (s);
1322 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1323 Real dy = pos.delta ();
1325 // ugh -> use commonx
1326 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
1327 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1328 Real dydx = dy && dx ? dy/dx : 0;
1329 dydx = 0.6 * tanh (dydx) / damping;
1331 Real damped_dy = dydx * dx;
1332 pos[LEFT] += (dy - damped_dy) / 2;
1333 pos[RIGHT] -= (dy - damped_dy) / 2;
1335 me->set_grob_property ("positions", ly_interval2scm (pos));
1337 return SCM_UNSPECIFIED;
1341 where_are_the_whole_beams(SCM beaming)
1345 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1347 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1349 l.add_point (gh_scm2int (gh_car (s)));
1356 Calculate the Y position of the stem-end, given the Y-left, Y-right
1357 in POS, and for stem S.
1359 If CORRECT, correct for beam_count of beam in case of knees.
1362 TODO: junk CORRECT from this.
1365 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool french)
1367 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1368 Real beam_space = get_beam_space (me);
1370 // ugh -> use commonx
1371 Grob * fvs = first_visible_stem (me);
1372 Grob *lvs = last_visible_stem (me);
1374 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1375 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1376 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1377 Real dy = pos.delta ();
1378 Real stem_y_beam0 = (dy && dx
1385 Direction my_dir = Directional_element_interface::get (s);
1386 SCM beaming = s->get_grob_property ("beaming");
1388 Real stem_y = stem_y_beam0;
1391 stem_y += beam_space * where_are_the_whole_beams (beaming)[-my_dir];
1395 stem_y += (stem_beam_multiplicity(s)[my_dir]) * beam_space;
1402 Hmm. At this time, beam position and slope are determined. Maybe,
1403 stem directions and length should set to relative to the chord's
1404 position of the beam. */
1406 Beam::set_stem_lengths (Grob *me)
1408 Link_array<Item> stems=
1409 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1411 if (stems.size () <= 1)
1414 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1415 for (int i=1; i < stems.size (); i++)
1416 if (!Stem::invisible_b (stems[i]))
1417 common = common->common_refpoint (stems[i], Y_AXIS);
1419 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1420 Real staff_space = Staff_symbol_referencer::staff_space (me);
1422 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1424 for (int i=0; i < stems.size (); i++)
1427 if (Stem::invisible_b (s))
1430 Real stem_y = calc_stem_y (me, s, pos, french && i > 0&& (i < stems.size () -1));
1432 /* caution: stem measures in staff-positions */
1433 Real id = me->relative_coordinate (common, Y_AXIS)
1434 - stems[i]->relative_coordinate (common, Y_AXIS);
1435 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1440 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1442 Link_array<Grob> stems=
1443 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1446 for (int i=0; i < stems.size (); i++)
1449 Don't overwrite user settings.
1454 /* Don't set beaming for outside of outer stems */
1455 if ((d == LEFT && i == 0)
1456 ||(d == RIGHT && i == stems.size () -1))
1460 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1461 if (beaming_prop == SCM_EOL ||
1462 index_get_cell (beaming_prop, d) == SCM_EOL)
1464 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1465 Stem::set_beaming (stems[i], b, d);
1468 while (flip (&d) != LEFT);
1473 Beam::forced_stem_count (Grob *me)
1475 Link_array<Item>stems =
1476 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1478 for (int i=0; i < stems.size (); i++)
1482 if (Stem::invisible_b (s))
1485 if (((int)Stem::chord_start_y (s))
1486 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1496 Beam::visible_stem_count (Grob *me)
1498 Link_array<Item>stems =
1499 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1501 for (int i = stems.size (); i--;)
1503 if (!Stem::invisible_b (stems[i]))
1510 Beam::first_visible_stem (Grob *me)
1512 Link_array<Item>stems =
1513 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1515 for (int i = 0; i < stems.size (); i++)
1517 if (!Stem::invisible_b (stems[i]))
1524 Beam::last_visible_stem (Grob *me)
1526 Link_array<Item>stems =
1527 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1528 for (int i = stems.size (); i--;)
1530 if (!Stem::invisible_b (stems[i]))
1540 handle rest under beam (do_post: beams are calculated now)
1541 what about combination of collisions and rest under beam.
1545 rest -> stem -> beam -> interpolate_y_position ()
1547 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1549 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1551 Grob *rest = unsmob_grob (element_smob);
1552 Axis a = (Axis) gh_scm2int (axis);
1554 assert (a == Y_AXIS);
1556 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1559 return gh_double2scm (0.0);
1560 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1562 || !Beam::has_interface (beam)
1563 || !Beam::visible_stem_count (beam))
1564 return gh_double2scm (0.0);
1566 // make callback for rest from this.
1567 // todo: make sure this calced already.
1569 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1570 Interval pos (0, 0);
1571 SCM s = beam->get_grob_property ("positions");
1572 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1573 pos = ly_scm2interval (s);
1575 Real dy = pos.delta ();
1576 // ugh -> use commonx
1577 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1578 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1579 Real dydx = dy && dx ? dy/dx : 0;
1581 Direction d = Stem::get_direction (stem);
1582 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1584 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1587 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1590 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1592 minimum_dist + -d * (beamy - rest_dim) >? 0;
1594 int stafflines = Staff_symbol_referencer::line_count (rest);
1596 // move discretely by half spaces.
1597 int discrete_dist = int (ceil (dist));
1599 // move by whole spaces inside the staff.
1600 if (discrete_dist < stafflines+1)
1601 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1603 return gh_double2scm (-d * discrete_dist);
1609 ADD_INTERFACE (Beam, "beam-interface",
1612 #'thickness= weight of beams, in staffspace
1615 We take the least squares line through the ideal-length stems, and
1616 then damp that using
1618 damped = tanh (slope)
1620 this gives an unquantized left and right position for the beam end.
1621 Then we take all combinations of quantings near these left and right
1622 positions, and give them a score (according to how close they are to
1623 the ideal slope, how close the result is to the ideal stems, etc.). We
1624 take the best scoring combination.
1627 "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");