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).
23 - Stems run to the Y-center of the beam.
25 - beam_space is the offset between Y centers of the beam.
30 #include <math.h> // tanh.
32 #include "molecule.hh"
33 #include "directional-element-interface.hh"
37 #include "least-squares.hh"
39 #include "paper-def.hh"
41 #include "group-interface.hh"
42 #include "staff-symbol-referencer.hh"
48 #define DEBUG_QUANTING 0
52 #include "text-item.hh" // debug output.
53 #include "font-interface.hh" // debug output.
57 const int INTER_QUANT_PENALTY = 1000;
58 const int SECONDARY_BEAM_DEMERIT = 15;
59 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
60 // possibly ridiculous, but too short stems just won't do
61 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
62 const int DAMPING_DIRECTIION_PENALTY = 800;
63 const int MUSICAL_DIRECTION_FACTOR = 400;
64 const int IDEAL_SLOPE_FACTOR = 10;
65 const int REGION_SIZE = 2;
69 shrink_extra_weight (Real x)
71 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
76 int_list_to_slice (SCM l)
80 for (; gh_pair_p (l); l = gh_cdr (l))
82 if (gh_number_p (gh_car (l)))
83 s.add_point (gh_scm2int (gh_car (l)));
91 stem_beam_multiplicity (Grob *stem)
93 SCM beaming= stem->get_grob_property ("beaming");
94 Slice l = int_list_to_slice (gh_car (beaming));
95 Slice r = int_list_to_slice (gh_cdr (beaming));
102 Beam::add_stem (Grob *me, Grob *s)
104 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
106 s->add_dependency (me);
108 assert (!Stem::beam_l (s));
109 s->set_grob_property ("beam", me->self_scm ());
111 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
115 Beam::get_beam_space (Grob *me)
117 SCM func = me->get_grob_property ("space-function");
118 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
119 return gh_scm2double (s);
126 Beam::get_beam_count (Grob *me)
129 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
131 Grob *sc = unsmob_grob (ly_car (s));
133 m = m >? (stem_beam_multiplicity (sc).length () + 1);
138 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
140 Beam::space_function (SCM smob, SCM beam_count)
142 Grob *me = unsmob_grob (smob);
144 Real staff_space = Staff_symbol_referencer::staff_space (me);
145 Real line = me->paper_l ()->get_var ("linethickness");
146 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
149 Real beam_space = gh_scm2int (beam_count) < 4
150 ? (2*staff_space + line - thickness) / 2.0
151 : (3*staff_space + line - thickness) / 3.0;
153 return gh_double2scm (beam_space);
157 /* After pre-processing all directions should be set.
158 Several post-processing routines (stem, slur, script) need stem/beam
160 Currenly, this means that beam has set all stem's directions.
161 [Alternatively, stems could set its own directions, according to
162 their beam, during 'final-pre-processing'.] */
163 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
165 Beam::before_line_breaking (SCM smob)
167 Grob *me = unsmob_grob (smob);
169 /* Beams with less than 2 two stems don't make much sense, but could happen
174 For a beam that only has one stem, we try to do some disappearance magic:
175 we revert the flag, and move on to The Eternal Engraving Fields. */
177 int count = visible_stem_count (me);
180 me->warning (_ ("beam has less than two visible stems"));
182 SCM stems = me->get_grob_property ("stems");
183 if (scm_ilength (stems) == 1)
185 me->warning (_ ("Beam has less than two stems. Removing beam."));
187 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
190 return SCM_UNSPECIFIED;
192 else if (scm_ilength (stems) == 0)
195 return SCM_UNSPECIFIED;
200 Direction d = get_default_dir (me);
202 consider_auto_knees (me, d);
203 set_stem_directions (me, d);
207 set_stem_shorten (me);
216 Beam::connect_beams (Grob *me)
218 Link_array<Grob> stems=
219 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
222 last_int.set_empty();
223 for (int i = 0; i< stems.size(); i++)
225 Grob *this_stem = stems[i];
226 SCM this_beaming = this_stem->get_grob_property ("beaming");
228 Direction this_dir = Directional_element_interface::get(this_stem);
231 int start_point = last_int [this_dir];
237 if (d == RIGHT && i == stems.size()-1)
240 new_slice.set_empty();
241 SCM s = index_get_cell (this_beaming, d);
242 for (; gh_pair_p (s); s = gh_cdr (s))
245 start_point - this_dir * gh_scm2int (gh_car (s));
247 new_slice.add_point (new_beam_pos);
248 gh_set_car_x (s, gh_int2scm (new_beam_pos));
251 while (flip (&d) != LEFT);
253 last_int = new_slice;
257 SCM s = gh_cdr (this_beaming);
258 for (; gh_pair_p (s); s = gh_cdr (s))
260 int np = - this_dir * gh_scm2int (gh_car(s));
261 gh_set_car_x (s, gh_int2scm (np));
262 last_int.add_point (np);
268 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
270 Beam::brew_molecule (SCM grob)
272 Grob *me = unsmob_grob (grob);
273 Link_array<Grob> stems=
274 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
275 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
278 if (visible_stem_count (me))
280 // ugh -> use commonx
281 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
282 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
286 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
287 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
290 SCM posns = me->get_grob_property ("positions");
292 if (!ly_number_pair_p (posns))
294 programming_error ("No beam posns");
295 pos = Interval (0,0);
298 pos= ly_scm2interval (posns);
300 Real dy = pos.delta ();
301 Real dydx = dy && dx ? dy/dx : 0;
303 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
304 Real bdy = get_beam_space (me);
306 SCM last_beaming = SCM_EOL;;
307 Real last_xposn = -1;
308 Real last_width = -1 ;
311 Real lt = me->paper_l ()->get_var ("linethickness");
312 for (int i = 0; i< stems.size(); i++)
316 SCM this_beaming = st->get_grob_property ("beaming");
317 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
318 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
322 SCM left = gh_cdr (last_beaming);
323 SCM right = gh_car (this_beaming);
325 Array<int> fullbeams;
326 Array<int> lfliebertjes;
327 Array<int> rfliebertjes;
330 gh_pair_p (s); s =gh_cdr (s))
332 int b = gh_scm2int (gh_car (s));
333 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
339 lfliebertjes.push (b);
343 gh_pair_p (s); s =gh_cdr (s))
345 int b = gh_scm2int (gh_car (s));
346 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
348 rfliebertjes.push (b);
353 Real w = xposn - last_xposn;
354 Real stem_offset = 0.0;
355 Real width_corr = 0.0;
358 stem_offset -= last_width/2;
359 width_corr += last_width/2;
362 if (i == stems.size() -1)
364 width_corr += stem_width/2;
367 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
368 for (int j = fullbeams.size(); j--;)
371 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
372 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
373 the_beam.add_molecule (b);
376 if (lfliebertjes.size() || rfliebertjes.size())
380 if (!Stem::first_head (st))
384 int t = Stem::duration_log (st);
386 SCM proc = me->get_grob_property ("flag-width-function");
387 SCM result = gh_call1 (proc, gh_int2scm (t));
388 nw_f = gh_scm2double (result);
391 /* Half beam should be one note-width,
392 but let's make sure two half-beams never touch */
394 Real w = xposn - last_xposn;
397 Molecule half = Lookup::beam (dydx, w, thick);
398 for (int j = lfliebertjes.size(); j--;)
401 b.translate_axis (last_xposn - x0, X_AXIS);
402 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
403 the_beam.add_molecule (b);
405 for (int j = rfliebertjes.size(); j--;)
408 b.translate_axis (xposn - x0 - w , X_AXIS);
409 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
410 the_beam.add_molecule (b);
416 last_width = stem_width;
417 last_beaming = this_beaming;
420 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
421 the_beam.translate_axis (pos[LEFT], Y_AXIS);
426 This code prints the demerits for each beam. Perhaps this
427 should be switchable for those who want to twiddle with the
433 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
436 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
439 SCM properties = Font_interface::font_alist_chain (me);
442 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
443 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
449 return the_beam.smobbed_copy();
456 Beam::get_default_dir (Grob *me)
458 Drul_array<int> total;
459 total[UP] = total[DOWN] = 0;
460 Drul_array<int> count;
461 count[UP] = count[DOWN] = 0;
464 Link_array<Item> stems=
465 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
467 for (int i=0; i <stems.size (); i++)
470 Direction sd = Directional_element_interface::get (s);
472 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
473 int current = sd ? (1 + d * sd)/2 : center_distance;
480 } while (flip (&d) != DOWN);
482 SCM func = me->get_grob_property ("dir-function");
483 SCM s = gh_call2 (func,
484 gh_cons (gh_int2scm (count[UP]),
485 gh_int2scm (count[DOWN])),
486 gh_cons (gh_int2scm (total[UP]),
487 gh_int2scm (total[DOWN])));
489 if (gh_number_p (s) && gh_scm2int (s))
492 /* If dir is not determined: get default */
493 return to_dir (me->get_grob_property ("neutral-direction"));
497 /* Set all stems with non-forced direction to beam direction.
498 Urg: non-forced should become `without/with unforced' direction,
499 once stem gets cleaned-up. */
501 Beam::set_stem_directions (Grob *me, Direction d)
503 Link_array<Item> stems
504 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
506 for (int i=0; i <stems.size (); i++)
509 SCM force = s->remove_grob_property ("dir-forced");
510 if (!gh_boolean_p (force) || !gh_scm2bool (force))
511 Directional_element_interface::set (s, d);
515 /* Simplistic auto-knees; only consider vertical gap between two
518 `Forced' stem directions are ignored. If you don't want auto-knees,
519 don't set, or unset auto-knee-gap. */
521 Beam::consider_auto_knees (Grob *me, Direction d)
523 SCM scm = me->get_grob_property ("auto-knee-gap");
525 if (gh_number_p (scm))
529 Real staff_space = Staff_symbol_referencer::staff_space (me);
530 Real gap = gh_scm2double (scm) / staff_space;
533 Link_array<Item> stems=
534 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
536 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
537 for (int i=1; i < stems.size (); i++)
538 if (!Stem::invisible_b (stems[i]))
539 common = common->common_refpoint (stems[i], Y_AXIS);
542 for (int i=1; i < stems.size (); i++)
544 if (!Stem::invisible_b (stems[i-1]))
546 if (Stem::invisible_b (stems[l]))
548 if (Stem::invisible_b (stems[i]))
551 Real left = Stem::extremal_heads (stems[l])[d]
552 ->relative_coordinate (common, Y_AXIS);
553 Real right = Stem::extremal_heads (stems[i])[-d]
554 ->relative_coordinate (common, Y_AXIS);
556 Real dy = right - left;
560 knee_y = (right + left) / 2;
568 for (int i=0; i < stems.size (); i++)
571 if (Stem::invisible_b (s) ||
572 s->get_grob_property ("dir-forced") == SCM_BOOL_T)
574 Real y = Stem::extremal_heads (stems[i])[d]
575 ->relative_coordinate (common, Y_AXIS);
577 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
578 s->set_grob_property ("dir-forced", SCM_BOOL_T);
584 /* Set stem's shorten property if unset.
587 take some y-position (chord/beam/nearest?) into account
588 scmify forced-fraction
592 why is shorten stored in beam, and not directly in stem?
596 Beam::set_stem_shorten (Grob *m)
598 Spanner*me = dynamic_cast<Spanner*> (m);
600 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
602 int beam_count = get_beam_count (me);
604 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
605 if (shorten == SCM_EOL)
608 int sz = scm_ilength (shorten);
610 Real staff_space = Staff_symbol_referencer::staff_space (me);
611 SCM shorten_elt = scm_list_ref (shorten,
612 gh_int2scm (beam_count <? (sz - 1)));
613 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
615 /* your similar cute comment here */
616 shorten_f *= forced_fraction;
619 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
622 /* Call list of y-dy-callbacks, that handle setting of
626 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
628 Beam::after_line_breaking (SCM smob)
630 Grob *me = unsmob_grob (smob);
632 /* Copy to mutable list. */
633 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
634 me->set_grob_property ("positions", s);
636 if (ly_car (s) == SCM_BOOL_F)
639 // one wonders if such genericity is necessary --hwn.
640 SCM callbacks = me->get_grob_property ("position-callbacks");
641 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
642 gh_call1 (ly_car (i), smob);
645 set_stem_lengths (me);
646 return SCM_UNSPECIFIED;
660 - Make all demerits customisable
662 - One sensible check per demerit (what's this --hwn)
664 - Add demerits for quants per se, as to forbid a specific quant
668 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
670 Beam::quanting (SCM smob)
672 Grob *me = unsmob_grob (smob);
674 SCM s = me->get_grob_property ("positions");
675 Real yl = gh_scm2double (gh_car (s));
676 Real yr = gh_scm2double (gh_cdr (s));
678 Real ss = Staff_symbol_referencer::staff_space (me);
679 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
680 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
683 SCM sdy = me->get_grob_property ("least-squares-dy");
684 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
687 Real sit = (thickness - slt) / 2;
689 Real hang = 1.0 - (thickness - slt) / 2;
690 Real quants [] = {straddle, sit, inter, hang };
692 int num_quants = int (sizeof (quants)/sizeof (Real));
697 going to REGION_SIZE == 2, yields another 0.6 second with
701 (result indexes between 70 and 575) ? --hwn.
708 Do stem computations. These depend on YL and YR linearly, so we can
709 precompute for every stem 2 factors.
711 Link_array<Grob> stems=
712 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
713 Array<Stem_info> stem_infos;
714 Array<Real> lbase_lengths;
715 Array<Real> rbase_lengths;
717 Drul_array<bool> dirs_found(0,0);
719 bool french = to_boolean (me->get_grob_property ("french-beaming"));
720 for (int i= 0; i < stems.size(); i++)
723 stem_infos.push (Stem::calc_stem_info (s));
724 dirs_found[stem_infos.top ().dir_] = true;
726 Real b = calc_stem_y (me, s, Interval (1,0), french && i > 0&& (i < stems.size () -1));
727 lbase_lengths.push (b);
729 Real a = calc_stem_y (me, s, Interval (0,1), french && i > 0&& (i < stems.size () -1));
730 rbase_lengths.push (a);
733 Direction ldir = Direction (stem_infos[0].dir_);
734 Direction rdir = Direction (stem_infos.top ().dir_);
735 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
738 int region_size = REGION_SIZE;
740 Knees are harder, lets try some more possibilities for knees.
745 for (int i = -region_size ; i < region_size; i++)
746 for (int j = 0; j < num_quants; j++)
748 quantsl.push (i + quants[j] + int (yl));
749 quantsr.push (i + quants[j] + int (yr));
752 Array<Quant_score> qscores;
754 for (int l =0; l < quantsl.size (); l++)
755 for (int r =0; r < quantsr.size (); r++)
767 This is a longish function, but we don't separate this out into
768 neat modular separate subfunctions, as the subfunctions would be
769 called for many values of YL, YR. By precomputing various
770 parameters outside of the loop, we can save a lot of time.
773 for (int i = qscores.size (); i--;)
774 if (qscores[i].demerits < 100)
777 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
781 Real rad = Staff_symbol_referencer::staff_radius (me);
782 int beam_count = get_beam_count (me);
783 Real beam_space = beam_count < 4
784 ? (2*ss + slt - thickness) / 2.0
785 : (3*ss + slt - thickness) / 3.0;
787 for (int i = qscores.size (); i--;)
788 if (qscores[i].demerits < 100)
791 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
792 rad, slt, thickness, beam_space,
793 beam_count, ldir, rdir);
797 for (int i = qscores.size (); i--;)
798 if (qscores[i].demerits < 100)
801 += score_stem_lengths (stems, stem_infos,
802 lbase_lengths, rbase_lengths,
804 me, qscores[i].yl, qscores[i].yr);
810 for (int i = qscores.size (); i--;)
812 if (qscores[i].demerits < best)
814 best = qscores [i].demerits ;
820 me->set_grob_property ("positions",
821 gh_cons (gh_double2scm (qscores[best_idx].yl),
822 gh_double2scm (qscores[best_idx].yr))
828 me->set_grob_property ("quant-score",
829 gh_double2scm (qscores[best_idx].demerits));
830 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
833 return SCM_UNSPECIFIED;
837 Beam::score_stem_lengths (Link_array<Grob>stems,
838 Array<Stem_info> stem_infos,
839 Array<Real> left_factor,
840 Array<Real> right_factor,
845 Real demerit_score = 0.0 ;
846 Real pen = STEM_LENGTH_LIMIT_PENALTY;
848 for (int i=0; i < stems.size (); i++)
851 if (Stem::invisible_b (s))
855 yl * left_factor[i] + right_factor[i]* yr;
857 Stem_info info = stem_infos[i];
858 Direction d = info.dir_;
861 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
863 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
864 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
867 demerit_score *= 2.0 / stems.size ();
869 return demerit_score;
873 Beam::score_slopes_dy (Grob *me,
875 Real dy_mus, Real dy_damp)
880 if (sign (dy_damp) != sign (dy))
882 dem += DAMPING_DIRECTIION_PENALTY;
885 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
886 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
894 return x - floor (x);
898 Beam::score_forbidden_quants (Grob*me,
902 Real thickness, Real beam_space,
904 Direction ldir, Direction rdir)
909 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
910 dem += INTER_QUANT_PENALTY;
911 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
912 dem += INTER_QUANT_PENALTY;
914 // todo: use beam_count of outer stems.
919 Real sit = (thickness - slt) / 2;
921 Real hang = 1.0 - (thickness - slt) / 2;
924 if (fabs (yl - ldir * beam_space) < rad
925 && fabs (my_modf (yl) - inter) < 1e-3)
926 dem += SECONDARY_BEAM_DEMERIT;
927 if (fabs (yr - rdir * beam_space) < rad
928 && fabs (my_modf (yr) - inter) < 1e-3)
929 dem += SECONDARY_BEAM_DEMERIT;
934 Can't we simply compute the distance between the nearest
935 staffline and the secondary beam? That would get rid of the
936 silly case analysis here (which is probably not when we have
937 different beam-thicknesses.)
943 // hmm, without Interval/Drul_array, you get ~ 4x same code...
944 if (fabs (yl - ldir * beam_space) < rad + inter)
946 if (ldir == UP && dy <= eps
947 && fabs (my_modf (yl) - sit) < eps)
948 dem += SECONDARY_BEAM_DEMERIT;
950 if (ldir == DOWN && dy >= eps
951 && fabs (my_modf (yl) - hang) < eps)
952 dem += SECONDARY_BEAM_DEMERIT;
955 if (fabs (yr - rdir * beam_space) < rad + inter)
957 if (rdir == UP && dy >= eps
958 && fabs (my_modf (yr) - sit) < eps)
959 dem += SECONDARY_BEAM_DEMERIT;
961 if (rdir == DOWN && dy <= eps
962 && fabs (my_modf (yr) - hang) < eps)
963 dem += SECONDARY_BEAM_DEMERIT;
968 if (fabs (yl - 2 * ldir * beam_space) < rad + inter)
970 if (ldir == UP && dy <= eps
971 && fabs (my_modf (yl) - straddle) < eps)
972 dem += SECONDARY_BEAM_DEMERIT;
974 if (ldir == DOWN && dy >= eps
975 && fabs (my_modf (yl) - straddle) < eps)
976 dem += SECONDARY_BEAM_DEMERIT;
979 if (fabs (yr - 2 * rdir * beam_space) < rad + inter)
981 if (rdir == UP && dy >= eps
982 && fabs (my_modf (yr) - straddle) < eps)
983 dem += SECONDARY_BEAM_DEMERIT;
985 if (rdir == DOWN && dy <= eps
986 && fabs (my_modf (yr) - straddle) < eps)
987 dem += SECONDARY_BEAM_DEMERIT;
997 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
999 Beam::least_squares (SCM smob)
1001 Grob *me = unsmob_grob (smob);
1003 int count = visible_stem_count (me);
1004 Interval pos (0, 0);
1008 me->set_grob_property ("positions", ly_interval2scm (pos));
1009 return SCM_UNSPECIFIED;
1012 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
1013 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
1017 Array<Real> x_posns ;
1018 Link_array<Item> stems=
1019 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1020 Grob *common = stems[0];
1021 for (int i=1; i < stems.size (); i++)
1022 common = stems[i]->common_refpoint (common, X_AXIS);
1024 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
1025 for (int i=0; i < stems.size (); i++)
1029 Real x = s->relative_coordinate (common, X_AXIS) - x0;
1032 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
1038 if (!ideal.delta ())
1040 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
1041 Stem::chord_start_y (last_visible_stem (me)));
1045 TODO -- use scoring for this.
1047 complicated, because we take stem-info.ideal for determining
1050 /* Make simple beam on middle line have small tilt */
1051 if (!ideal[LEFT] && chord.delta () && count == 2)
1057 Direction d = (Direction) (sign (chord.delta ()) * UP);
1058 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
1073 Array<Offset> ideals;
1074 for (int i=0; i < stems.size (); i++)
1077 if (Stem::invisible_b (s))
1079 ideals.push (Offset (x_posns[i],
1080 Stem::calc_stem_info (s).ideal_y_));
1082 minimise_least_squares (&dydx, &y, ideals);
1085 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
1086 pos = Interval (y, (y+dy));
1089 me->set_grob_property ("positions", ly_interval2scm (pos));
1091 return SCM_UNSPECIFIED;
1096 We can't combine with previous function, since check concave and
1097 slope damping comes first.
1099 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
1101 Beam::shift_region_to_valid (SCM grob)
1103 Grob *me = unsmob_grob (grob);
1107 Array<Real> x_posns ;
1108 Link_array<Item> stems=
1109 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1110 Grob *common = stems[0];
1111 for (int i=1; i < stems.size (); i++)
1112 common = stems[i]->common_refpoint (common, X_AXIS);
1114 Grob *fvs = first_visible_stem (me);
1117 return SCM_UNSPECIFIED;
1119 Real x0 =fvs->relative_coordinate (common, X_AXIS);
1120 for (int i=0; i < stems.size (); i++)
1124 Real x = s->relative_coordinate (common, X_AXIS) - x0;
1128 Grob *lvs = last_visible_stem (me);
1130 return SCM_UNSPECIFIED;
1132 Real dx = lvs->relative_coordinate (common, X_AXIS) - x0;
1134 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
1135 Real dy = pos.delta();
1141 Shift the positions so that we have a chance of finding good
1142 quants (i.e. no short stem failures.)
1144 Interval feasible_left_point;
1145 feasible_left_point.set_full ();
1146 for (int i=0; i < stems.size (); i++)
1149 if (Stem::invisible_b (s))
1153 Direction d = Stem::get_direction (s);
1156 Real left_y = Stem::calc_stem_info (s).shortest_y_
1157 - dydx * x_posns [i];
1163 feasible_left_point.intersect (flp);
1166 if (feasible_left_point.empty_b())
1168 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1170 else if (!feasible_left_point.elem_b(y))
1172 if (isinf (feasible_left_point[DOWN]))
1173 y = feasible_left_point[UP] - REGION_SIZE;
1174 else if (isinf (feasible_left_point[UP]))
1175 y = feasible_left_point[DOWN]+ REGION_SIZE;
1177 y = feasible_left_point.center ();
1179 pos = Interval (y, (y+dy));
1180 me->set_grob_property ("positions", ly_interval2scm (pos));
1181 return SCM_UNSPECIFIED;
1185 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1187 Beam::check_concave (SCM smob)
1189 Grob *me = unsmob_grob (smob);
1191 Link_array<Item> stems =
1192 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1194 for (int i = 0; i < stems.size ();)
1196 if (Stem::invisible_b (stems[i]))
1202 if (stems.size () < 3)
1203 return SCM_UNSPECIFIED;
1206 /* Concaveness #1: If distance of an inner notehead to line between
1207 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1208 beam is concave (Heinz Stolba).
1210 In the case of knees, the line connecting outer heads is often
1211 not related to the beam slope (it may even go in the other
1212 direction). Skip the check when the outer stems point in
1213 different directions. --hwn
1216 bool concaveness1 = false;
1217 SCM gap = me->get_grob_property ("concaveness-gap");
1218 if (gh_number_p (gap)
1219 && Stem::get_direction(stems.top ())
1220 == Stem::get_direction(stems[0]))
1222 Real r1 = gh_scm2double (gap);
1223 Real dy = Stem::chord_start_y (stems.top ())
1224 - Stem::chord_start_y (stems[0]);
1227 Real slope = dy / (stems.size () - 1);
1229 Real y0 = Stem::chord_start_y (stems[0]);
1230 for (int i = 1; i < stems.size () - 1; i++)
1232 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1235 concaveness1 = true;
1242 /* Concaveness #2: Sum distances of inner noteheads that fall
1243 outside the interval of the two outer noteheads.
1245 We only do this for beams where first and last stem have the same
1249 Note that "convex" stems compensate for "concave" stems.
1250 (is that intentional?) --hwn.
1253 Real concaveness2 = 0;
1254 SCM thresh = me->get_grob_property ("concaveness-threshold");
1255 Real r2 = infinity_f;
1256 if (!concaveness1 && gh_number_p (thresh)
1257 && Stem::get_direction(stems.top ())
1258 == Stem::get_direction(stems[0]))
1260 r2 = gh_scm2double (thresh);
1262 Direction dir = Stem::get_direction(stems.top ());
1264 Interval iv (Stem::chord_start_y (stems[0]),
1265 Stem::chord_start_y (stems.top ()));
1267 if (iv[MAX] < iv[MIN])
1270 for (int i = 1; i < stems.size () - 1; i++)
1272 Real f = Stem::chord_start_y (stems[i]);
1273 concave += ((f - iv[MAX] ) >? 0) +
1274 ((f - iv[MIN] ) <? 0);
1277 concaveness2 = concave / (stems.size () - 2);
1279 /* ugh: this is the a kludge to get
1280 input/regression/beam-concave.ly to behave as
1284 huh? we're dividing twice (which is not scalable) meaning that
1285 the longer the beam, the more unlikely it will be
1286 concave. Maybe you would even expect the other way around??
1291 concaveness2 /= (stems.size () - 2);
1294 /* TODO: some sort of damping iso -> plain horizontal */
1295 if (concaveness1 || concaveness2 > r2)
1297 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1298 Real r = pos.linear_combination (0);
1299 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1300 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1303 return SCM_UNSPECIFIED;
1306 /* This neat trick is by Werner Lemberg,
1307 damped = tanh (slope)
1308 corresponds with some tables in [Wanske] CHECKME */
1309 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1311 Beam::slope_damping (SCM smob)
1313 Grob *me = unsmob_grob (smob);
1315 if (visible_stem_count (me) <= 1)
1316 return SCM_UNSPECIFIED;
1318 SCM s = me->get_grob_property ("damping");
1319 int damping = gh_scm2int (s);
1323 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1324 Real dy = pos.delta ();
1326 // ugh -> use commonx
1327 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
1328 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1329 Real dydx = dy && dx ? dy/dx : 0;
1330 dydx = 0.6 * tanh (dydx) / damping;
1332 Real damped_dy = dydx * dx;
1333 pos[LEFT] += (dy - damped_dy) / 2;
1334 pos[RIGHT] -= (dy - damped_dy) / 2;
1336 me->set_grob_property ("positions", ly_interval2scm (pos));
1338 return SCM_UNSPECIFIED;
1342 where_are_the_whole_beams(SCM beaming)
1346 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1348 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1350 l.add_point (gh_scm2int (gh_car (s)));
1357 Calculate the Y position of the stem-end, given the Y-left, Y-right
1358 in POS, and for stem S.
1361 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool french)
1363 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1364 Real beam_space = get_beam_space (me);
1366 // ugh -> use commonx
1367 Grob * fvs = first_visible_stem (me);
1368 Grob *lvs = last_visible_stem (me);
1370 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1371 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1372 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1373 Real dy = pos.delta ();
1374 Real stem_y_beam0 = (dy && dx
1381 Direction my_dir = Directional_element_interface::get (s);
1382 SCM beaming = s->get_grob_property ("beaming");
1384 Real stem_y = stem_y_beam0;
1387 stem_y += beam_space * where_are_the_whole_beams (beaming)[-my_dir];
1391 stem_y += (stem_beam_multiplicity(s)[my_dir]) * beam_space;
1398 Hmm. At this time, beam position and slope are determined. Maybe,
1399 stem directions and length should set to relative to the chord's
1400 position of the beam. */
1402 Beam::set_stem_lengths (Grob *me)
1404 Link_array<Item> stems=
1405 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1407 if (stems.size () <= 1)
1410 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1411 for (int i=1; i < stems.size (); i++)
1412 if (!Stem::invisible_b (stems[i]))
1413 common = common->common_refpoint (stems[i], Y_AXIS);
1415 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1416 Real staff_space = Staff_symbol_referencer::staff_space (me);
1418 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1420 for (int i=0; i < stems.size (); i++)
1423 if (Stem::invisible_b (s))
1426 Real stem_y = calc_stem_y (me, s, pos, french && i > 0&& (i < stems.size () -1));
1428 /* caution: stem measures in staff-positions */
1429 Real id = me->relative_coordinate (common, Y_AXIS)
1430 - stems[i]->relative_coordinate (common, Y_AXIS);
1431 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1436 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1438 Link_array<Grob> stems=
1439 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1442 for (int i=0; i < stems.size (); i++)
1445 Don't overwrite user settings.
1450 /* Don't set beaming for outside of outer stems */
1451 if ((d == LEFT && i == 0)
1452 ||(d == RIGHT && i == stems.size () -1))
1456 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1457 if (beaming_prop == SCM_EOL ||
1458 index_get_cell (beaming_prop, d) == SCM_EOL)
1460 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1461 Stem::set_beaming (stems[i], b, d);
1464 while (flip (&d) != LEFT);
1469 Beam::forced_stem_count (Grob *me)
1471 Link_array<Item>stems =
1472 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1474 for (int i=0; i < stems.size (); i++)
1478 if (Stem::invisible_b (s))
1481 if (((int)Stem::chord_start_y (s))
1482 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1492 Beam::visible_stem_count (Grob *me)
1494 Link_array<Item>stems =
1495 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1497 for (int i = stems.size (); i--;)
1499 if (!Stem::invisible_b (stems[i]))
1506 Beam::first_visible_stem (Grob *me)
1508 Link_array<Item>stems =
1509 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1511 for (int i = 0; i < stems.size (); i++)
1513 if (!Stem::invisible_b (stems[i]))
1520 Beam::last_visible_stem (Grob *me)
1522 Link_array<Item>stems =
1523 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1524 for (int i = stems.size (); i--;)
1526 if (!Stem::invisible_b (stems[i]))
1536 handle rest under beam (do_post: beams are calculated now)
1537 what about combination of collisions and rest under beam.
1541 rest -> stem -> beam -> interpolate_y_position ()
1543 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1545 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1547 Grob *rest = unsmob_grob (element_smob);
1548 Axis a = (Axis) gh_scm2int (axis);
1550 assert (a == Y_AXIS);
1552 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1555 return gh_double2scm (0.0);
1556 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1558 || !Beam::has_interface (beam)
1559 || !Beam::visible_stem_count (beam))
1560 return gh_double2scm (0.0);
1562 // make callback for rest from this.
1563 // todo: make sure this calced already.
1565 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1566 Interval pos (0, 0);
1567 SCM s = beam->get_grob_property ("positions");
1568 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1569 pos = ly_scm2interval (s);
1571 Real dy = pos.delta ();
1572 // ugh -> use commonx
1573 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1574 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1575 Real dydx = dy && dx ? dy/dx : 0;
1577 Direction d = Stem::get_direction (stem);
1578 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1580 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1583 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1586 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1588 minimum_dist + -d * (beamy - rest_dim) >? 0;
1590 int stafflines = Staff_symbol_referencer::line_count (rest);
1592 // move discretely by half spaces.
1593 int discrete_dist = int (ceil (dist));
1595 // move by whole spaces inside the staff.
1596 if (discrete_dist < stafflines+1)
1597 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1599 return gh_double2scm (-d * discrete_dist);
1605 ADD_INTERFACE (Beam, "beam-interface",
1608 #'thickness= weight of beams, in staffspace
1611 We take the least squares line through the ideal-length stems, and
1612 then damp that using
1614 damped = tanh (slope)
1616 this gives an unquantized left and right position for the beam end.
1617 Then we take all combinations of quantings near these left and right
1618 positions, and give them a score (according to how close they are to
1619 the ideal slope, how close the result is to the ideal stems, etc.). We
1620 take the best scoring combination.
1623 "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");