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).
21 - Determine auto knees based on positions if it's set by the user.
27 - Stems run to the Y-center of the beam.
29 - beam_space is the offset between Y centers of the beam.
34 #include <math.h> // tanh.
36 #include "molecule.hh"
37 #include "directional-element-interface.hh"
41 #include "least-squares.hh"
43 #include "paper-def.hh"
45 #include "group-interface.hh"
46 #include "staff-symbol-referencer.hh"
52 #define DEBUG_QUANTING 0
56 #include "text-item.hh" // debug output.
57 #include "font-interface.hh" // debug output.
61 const int INTER_QUANT_PENALTY = 1000;
62 const int SECONDARY_BEAM_DEMERIT = 15;
63 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
64 // possibly ridiculous, but too short stems just won't do
65 const int STEM_LENGTH_LIMIT_PENALTY = 5000;
66 const int DAMPING_DIRECTIION_PENALTY = 800;
67 const int MUSICAL_DIRECTION_FACTOR = 400;
68 const int IDEAL_SLOPE_FACTOR = 10;
69 const int REGION_SIZE = 2;
73 shrink_extra_weight (Real x)
75 return fabs (x) * ((x < 0) ? 1.5 : 1.0);
79 Beam::add_stem (Grob *me, Grob *s)
81 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
83 s->add_dependency (me);
85 assert (!Stem::beam_l (s));
86 s->set_grob_property ("beam", me->self_scm ());
88 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
92 Beam::get_beam_space (Grob *me)
94 SCM func = me->get_grob_property ("space-function");
95 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
96 return gh_scm2double (s);
103 Beam::get_beam_count (Grob *me)
106 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
108 Grob *sc = unsmob_grob (ly_car (s));
110 m = m >? (Stem::beam_multiplicity (sc).length () + 1);
115 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
117 Beam::space_function (SCM smob, SCM beam_count)
119 Grob *me = unsmob_grob (smob);
121 Real staff_space = Staff_symbol_referencer::staff_space (me);
122 Real line = me->paper_l ()->get_var ("linethickness");
123 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
126 Real beam_space = gh_scm2int (beam_count) < 4
127 ? (2*staff_space + line - thickness) / 2.0
128 : (3*staff_space + line - thickness) / 3.0;
130 return gh_double2scm (beam_space);
134 /* After pre-processing all directions should be set.
135 Several post-processing routines (stem, slur, script) need stem/beam
137 Currenly, this means that beam has set all stem's directions.
138 [Alternatively, stems could set its own directions, according to
139 their beam, during 'final-pre-processing'.] */
140 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
142 Beam::before_line_breaking (SCM smob)
144 Grob *me = unsmob_grob (smob);
146 /* Beams with less than 2 two stems don't make much sense, but could happen
151 For a beam that only has one stem, we try to do some disappearance magic:
152 we revert the flag, and move on to The Eternal Engraving Fields. */
154 int count = visible_stem_count (me);
157 me->warning (_ ("beam has less than two visible stems"));
159 SCM stems = me->get_grob_property ("stems");
160 if (scm_ilength (stems) == 1)
162 me->warning (_ ("Beam has less than two stems. Removing beam."));
164 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
167 return SCM_UNSPECIFIED;
169 else if (scm_ilength (stems) == 0)
172 return SCM_UNSPECIFIED;
177 Direction d = get_default_dir (me);
179 consider_auto_knees (me, d);
180 set_stem_directions (me, d);
184 set_stem_shorten (me);
193 Beam::connect_beams (Grob *me)
195 Link_array<Grob> stems=
196 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
199 last_int.set_empty();
200 for (int i = 0; i< stems.size(); i++)
202 Grob *this_stem = stems[i];
203 SCM this_beaming = this_stem->get_grob_property ("beaming");
205 Direction this_dir = Directional_element_interface::get(this_stem);
208 int start_point = last_int [this_dir];
214 if (d == RIGHT && i == stems.size()-1)
217 new_slice.set_empty();
218 SCM s = index_get_cell (this_beaming, d);
219 for (; gh_pair_p (s); s = gh_cdr (s))
222 start_point - this_dir * gh_scm2int (gh_car (s));
224 new_slice.add_point (new_beam_pos);
225 gh_set_car_x (s, gh_int2scm (new_beam_pos));
228 while (flip (&d) != LEFT);
230 if (!new_slice.empty_b())
231 last_int = new_slice;
235 SCM s = gh_cdr (this_beaming);
236 for (; gh_pair_p (s); s = gh_cdr (s))
238 int np = - this_dir * gh_scm2int (gh_car(s));
239 gh_set_car_x (s, gh_int2scm (np));
240 last_int.add_point (np);
246 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
248 Beam::brew_molecule (SCM grob)
250 Grob *me = unsmob_grob (grob);
251 Link_array<Grob> stems=
252 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
253 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
256 if (visible_stem_count (me))
258 // ugh -> use commonx
259 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
260 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
264 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
265 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
268 SCM posns = me->get_grob_property ("positions");
270 if (!ly_number_pair_p (posns))
272 programming_error ("No beam posns");
273 pos = Interval (0,0);
276 pos= ly_scm2interval (posns);
278 Real dy = pos.delta ();
279 Real dydx = dy && dx ? dy/dx : 0;
281 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
282 Real bdy = get_beam_space (me);
284 SCM last_beaming = SCM_EOL;;
285 Real last_xposn = -1;
286 Real last_width = -1 ;
289 SCM gap = me->get_grob_property ("gap");
291 Real lt = me->paper_l ()->get_var ("linethickness");
292 for (int i = 0; i< stems.size(); i++)
296 SCM this_beaming = st->get_grob_property ("beaming");
297 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
298 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
302 SCM left = gh_cdr (last_beaming);
303 SCM right = gh_car (this_beaming);
305 Array<int> fullbeams;
306 Array<int> lfliebertjes;
307 Array<int> rfliebertjes;
310 gh_pair_p (s); s =gh_cdr (s))
312 int b = gh_scm2int (gh_car (s));
313 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
319 lfliebertjes.push (b);
323 gh_pair_p (s); s =gh_cdr (s))
325 int b = gh_scm2int (gh_car (s));
326 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
328 rfliebertjes.push (b);
333 Real w = xposn - last_xposn;
334 Real stem_offset = 0.0;
335 Real width_corr = 0.0;
338 stem_offset -= last_width/2;
339 width_corr += last_width/2;
342 if (i == stems.size() -1)
344 width_corr += stem_width/2;
347 if (gh_number_p (gap))
349 Real g = gh_scm2double (gap);
354 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
355 for (int j = fullbeams.size(); j--;)
358 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
359 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
360 the_beam.add_molecule (b);
363 if (lfliebertjes.size() || rfliebertjes.size())
367 if (!Stem::first_head (st))
371 int t = Stem::duration_log (st);
373 SCM proc = me->get_grob_property ("flag-width-function");
374 SCM result = gh_call1 (proc, gh_int2scm (t));
375 nw_f = gh_scm2double (result);
378 /* Half beam should be one note-width,
379 but let's make sure two half-beams never touch */
381 Real w = xposn - last_xposn;
384 Molecule half = Lookup::beam (dydx, w, thick);
385 for (int j = lfliebertjes.size(); j--;)
388 b.translate_axis (last_xposn - x0, X_AXIS);
389 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
390 the_beam.add_molecule (b);
392 for (int j = rfliebertjes.size(); j--;)
395 b.translate_axis (xposn - x0 - w , X_AXIS);
396 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
397 the_beam.add_molecule (b);
403 last_width = stem_width;
404 last_beaming = this_beaming;
407 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
408 the_beam.translate_axis (pos[LEFT], Y_AXIS);
413 This code prints the demerits for each beam. Perhaps this
414 should be switchable for those who want to twiddle with the
420 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
423 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
426 SCM properties = Font_interface::font_alist_chain (me);
429 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
430 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
436 return the_beam.smobbed_copy();
443 Beam::get_default_dir (Grob *me)
445 Drul_array<int> total;
446 total[UP] = total[DOWN] = 0;
447 Drul_array<int> count;
448 count[UP] = count[DOWN] = 0;
451 Link_array<Item> stems=
452 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
454 for (int i=0; i <stems.size (); i++)
457 Direction sd = Directional_element_interface::get (s);
459 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
460 int current = sd ? (1 + d * sd)/2 : center_distance;
467 } while (flip (&d) != DOWN);
469 SCM func = me->get_grob_property ("dir-function");
470 SCM s = gh_call2 (func,
471 gh_cons (gh_int2scm (count[UP]),
472 gh_int2scm (count[DOWN])),
473 gh_cons (gh_int2scm (total[UP]),
474 gh_int2scm (total[DOWN])));
476 if (gh_number_p (s) && gh_scm2int (s))
479 /* If dir is not determined: get default */
480 return to_dir (me->get_grob_property ("neutral-direction"));
484 /* Set all stems with non-forced direction to beam direction.
485 Urg: non-forced should become `without/with unforced' direction,
486 once stem gets cleaned-up. */
488 Beam::set_stem_directions (Grob *me, Direction d)
490 Link_array<Item> stems
491 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
493 for (int i=0; i <stems.size (); i++)
496 SCM force = s->remove_grob_property ("dir-forced");
497 if (!gh_boolean_p (force) || !gh_scm2bool (force))
498 Directional_element_interface::set (s, d);
502 /* Simplistic auto-knees; only consider vertical gap between two
505 `Forced' stem directions are ignored. If you don't want auto-knees,
506 don't set, or unset auto-knee-gap. */
508 Beam::consider_auto_knees (Grob *me, Direction d)
510 SCM scm = me->get_grob_property ("auto-knee-gap");
512 if (gh_number_p (scm))
516 Real staff_space = Staff_symbol_referencer::staff_space (me);
517 Real gap = gh_scm2double (scm) / staff_space;
520 Link_array<Item> stems=
521 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
523 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
524 for (int i=1; i < stems.size (); i++)
525 if (!Stem::invisible_b (stems[i]))
526 common = common->common_refpoint (stems[i], Y_AXIS);
529 for (int i=1; i < stems.size (); i++)
531 if (!Stem::invisible_b (stems[i-1]))
533 if (Stem::invisible_b (stems[l]))
535 if (Stem::invisible_b (stems[i]))
538 Real left = Stem::extremal_heads (stems[l])[d]
539 ->relative_coordinate (common, Y_AXIS);
540 Real right = Stem::extremal_heads (stems[i])[-d]
541 ->relative_coordinate (common, Y_AXIS);
543 Real dy = right - left;
547 knee_y = (right + left) / 2;
555 for (int i=0; i < stems.size (); i++)
558 if (Stem::invisible_b (s) ||
559 s->get_grob_property ("dir-forced") == SCM_BOOL_T)
561 Real y = Stem::extremal_heads (stems[i])[d]
562 ->relative_coordinate (common, Y_AXIS);
564 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
565 s->set_grob_property ("dir-forced", SCM_BOOL_T);
571 /* Set stem's shorten property if unset.
574 take some y-position (chord/beam/nearest?) into account
575 scmify forced-fraction
579 why is shorten stored in beam, and not directly in stem?
583 Beam::set_stem_shorten (Grob *m)
585 Spanner*me = dynamic_cast<Spanner*> (m);
587 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
589 int beam_count = get_beam_count (me);
591 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
592 if (shorten == SCM_EOL)
595 int sz = scm_ilength (shorten);
597 Real staff_space = Staff_symbol_referencer::staff_space (me);
598 SCM shorten_elt = scm_list_ref (shorten,
599 gh_int2scm (beam_count <? (sz - 1)));
600 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
602 /* your similar cute comment here */
603 shorten_f *= forced_fraction;
606 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
609 /* Call list of y-dy-callbacks, that handle setting of
613 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
615 Beam::after_line_breaking (SCM smob)
617 Grob *me = unsmob_grob (smob);
619 /* Copy to mutable list. */
620 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
621 me->set_grob_property ("positions", s);
623 if (ly_car (s) == SCM_BOOL_F)
626 // one wonders if such genericity is necessary --hwn.
627 SCM callbacks = me->get_grob_property ("position-callbacks");
628 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
629 gh_call1 (ly_car (i), smob);
632 set_stem_lengths (me);
633 return SCM_UNSPECIFIED;
647 - Make all demerits customisable
649 - One sensible check per demerit (what's this --hwn)
651 - Add demerits for quants per se, as to forbid a specific quant
655 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
657 Beam::quanting (SCM smob)
659 Grob *me = unsmob_grob (smob);
661 SCM s = me->get_grob_property ("positions");
662 Real yl = gh_scm2double (gh_car (s));
663 Real yr = gh_scm2double (gh_cdr (s));
665 Real ss = Staff_symbol_referencer::staff_space (me);
666 Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
667 Real slt = me->paper_l ()->get_var ("linethickness") / ss;
670 SCM sdy = me->get_grob_property ("least-squares-dy");
671 Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
674 Real sit = (thickness - slt) / 2;
676 Real hang = 1.0 - (thickness - slt) / 2;
677 Real quants [] = {straddle, sit, inter, hang };
679 int num_quants = int (sizeof (quants)/sizeof (Real));
684 going to REGION_SIZE == 2, yields another 0.6 second with
688 (result indexes between 70 and 575) ? --hwn.
695 Do stem computations. These depend on YL and YR linearly, so we can
696 precompute for every stem 2 factors.
698 Link_array<Grob> stems=
699 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
700 Array<Stem_info> stem_infos;
701 Array<Real> lbase_lengths;
702 Array<Real> rbase_lengths;
704 Drul_array<bool> dirs_found(0,0);
706 bool french = to_boolean (me->get_grob_property ("french-beaming"));
707 for (int i= 0; i < stems.size(); i++)
710 stem_infos.push (Stem::calc_stem_info (s));
711 dirs_found[stem_infos.top ().dir_] = true;
713 Real b = calc_stem_y (me, s, Interval (1,0), french && i > 0&& (i < stems.size () -1));
714 lbase_lengths.push (b);
716 Real a = calc_stem_y (me, s, Interval (0,1), french && i > 0&& (i < stems.size () -1));
717 rbase_lengths.push (a);
720 Direction ldir = Direction (stem_infos[0].dir_);
721 Direction rdir = Direction (stem_infos.top ().dir_);
722 bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
725 int region_size = REGION_SIZE;
727 Knees are harder, lets try some more possibilities for knees.
732 for (int i = -region_size ; i < region_size; i++)
733 for (int j = 0; j < num_quants; j++)
735 quantsl.push (i + quants[j] + int (yl));
736 quantsr.push (i + quants[j] + int (yr));
739 Array<Quant_score> qscores;
741 for (int l =0; l < quantsl.size (); l++)
742 for (int r =0; r < quantsr.size (); r++)
754 This is a longish function, but we don't separate this out into
755 neat modular separate subfunctions, as the subfunctions would be
756 called for many values of YL, YR. By precomputing various
757 parameters outside of the loop, we can save a lot of time.
760 for (int i = qscores.size (); i--;)
761 if (qscores[i].demerits < 100)
764 += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
768 Real rad = Staff_symbol_referencer::staff_radius (me);
769 int beam_count = get_beam_count (me);
770 Real beam_space = beam_count < 4
771 ? (2*ss + slt - thickness) / 2.0
772 : (3*ss + slt - thickness) / 3.0;
774 for (int i = qscores.size (); i--;)
775 if (qscores[i].demerits < 100)
778 += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
779 rad, slt, thickness, beam_space,
780 beam_count, ldir, rdir);
784 for (int i = qscores.size (); i--;)
785 if (qscores[i].demerits < 100)
788 += score_stem_lengths (stems, stem_infos,
789 lbase_lengths, rbase_lengths,
791 me, qscores[i].yl, qscores[i].yr);
797 for (int i = qscores.size (); i--;)
799 if (qscores[i].demerits < best)
801 best = qscores [i].demerits ;
807 me->set_grob_property ("positions",
808 gh_cons (gh_double2scm (qscores[best_idx].yl),
809 gh_double2scm (qscores[best_idx].yr))
815 me->set_grob_property ("quant-score",
816 gh_double2scm (qscores[best_idx].demerits));
817 me->set_grob_property ("best-idx", gh_int2scm (best_idx));
820 return SCM_UNSPECIFIED;
824 Beam::score_stem_lengths (Link_array<Grob>stems,
825 Array<Stem_info> stem_infos,
826 Array<Real> left_factor,
827 Array<Real> right_factor,
832 Real demerit_score = 0.0 ;
833 Real pen = STEM_LENGTH_LIMIT_PENALTY;
835 for (int i=0; i < stems.size (); i++)
838 if (Stem::invisible_b (s))
842 yl * left_factor[i] + right_factor[i]* yr;
844 Stem_info info = stem_infos[i];
845 Direction d = info.dir_;
848 * ( 0 >? (info.dir_ * (info.shortest_y_ - current_y)));
850 demerit_score += STEM_LENGTH_DEMERIT_FACTOR
851 * shrink_extra_weight (d * current_y - info.dir_ * info.ideal_y_);
854 demerit_score *= 2.0 / stems.size ();
856 return demerit_score;
860 Beam::score_slopes_dy (Grob *me,
862 Real dy_mus, Real dy_damp)
867 if (sign (dy_damp) != sign (dy))
869 dem += DAMPING_DIRECTIION_PENALTY;
872 dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
873 dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
881 return x - floor (x);
885 Beam::score_forbidden_quants (Grob*me,
889 Real thickness, Real beam_space,
891 Direction ldir, Direction rdir)
896 if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
897 dem += INTER_QUANT_PENALTY;
898 if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
899 dem += INTER_QUANT_PENALTY;
901 // todo: use beam_count of outer stems.
906 Real sit = (thickness - slt) / 2;
908 Real hang = 1.0 - (thickness - slt) / 2;
911 if (fabs (yl - ldir * beam_space) < rad
912 && fabs (my_modf (yl) - inter) < 1e-3)
913 dem += SECONDARY_BEAM_DEMERIT;
914 if (fabs (yr - rdir * beam_space) < rad
915 && fabs (my_modf (yr) - inter) < 1e-3)
916 dem += SECONDARY_BEAM_DEMERIT;
921 Can't we simply compute the distance between the nearest
922 staffline and the secondary beam? That would get rid of the
923 silly case analysis here (which is probably not when we have
924 different beam-thicknesses.)
930 // hmm, without Interval/Drul_array, you get ~ 4x same code...
931 if (fabs (yl - ldir * beam_space) < rad + inter)
933 if (ldir == UP && dy <= eps
934 && fabs (my_modf (yl) - sit) < eps)
935 dem += SECONDARY_BEAM_DEMERIT;
937 if (ldir == DOWN && dy >= eps
938 && fabs (my_modf (yl) - hang) < eps)
939 dem += SECONDARY_BEAM_DEMERIT;
942 if (fabs (yr - rdir * beam_space) < rad + inter)
944 if (rdir == UP && dy >= eps
945 && fabs (my_modf (yr) - sit) < eps)
946 dem += SECONDARY_BEAM_DEMERIT;
948 if (rdir == DOWN && dy <= eps
949 && fabs (my_modf (yr) - hang) < eps)
950 dem += SECONDARY_BEAM_DEMERIT;
955 if (fabs (yl - 2 * ldir * beam_space) < rad + inter)
957 if (ldir == UP && dy <= eps
958 && fabs (my_modf (yl) - straddle) < eps)
959 dem += SECONDARY_BEAM_DEMERIT;
961 if (ldir == DOWN && dy >= eps
962 && fabs (my_modf (yl) - straddle) < eps)
963 dem += SECONDARY_BEAM_DEMERIT;
966 if (fabs (yr - 2 * rdir * beam_space) < rad + inter)
968 if (rdir == UP && dy >= eps
969 && fabs (my_modf (yr) - straddle) < eps)
970 dem += SECONDARY_BEAM_DEMERIT;
972 if (rdir == DOWN && dy <= eps
973 && fabs (my_modf (yr) - straddle) < eps)
974 dem += SECONDARY_BEAM_DEMERIT;
984 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
986 Beam::least_squares (SCM smob)
988 Grob *me = unsmob_grob (smob);
990 int count = visible_stem_count (me);
995 me->set_grob_property ("positions", ly_interval2scm (pos));
996 return SCM_UNSPECIFIED;
999 Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
1000 Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
1004 Array<Real> x_posns ;
1005 Link_array<Item> stems=
1006 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1007 Grob *common = stems[0];
1008 for (int i=1; i < stems.size (); i++)
1009 common = stems[i]->common_refpoint (common, X_AXIS);
1011 Real x0 = first_visible_stem (me)->relative_coordinate (common, X_AXIS);
1012 for (int i=0; i < stems.size (); i++)
1016 Real x = s->relative_coordinate (common, X_AXIS) - x0;
1019 Real dx = last_visible_stem (me)->relative_coordinate (common, X_AXIS) - x0;
1025 if (!ideal.delta ())
1027 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
1028 Stem::chord_start_y (last_visible_stem (me)));
1032 TODO -- use scoring for this.
1034 complicated, because we take stem-info.ideal for determining
1037 /* Make simple beam on middle line have small tilt */
1038 if (!ideal[LEFT] && chord.delta () && count == 2)
1044 Direction d = (Direction) (sign (chord.delta ()) * UP);
1045 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
1060 Array<Offset> ideals;
1061 for (int i=0; i < stems.size (); i++)
1064 if (Stem::invisible_b (s))
1066 ideals.push (Offset (x_posns[i],
1067 Stem::calc_stem_info (s).ideal_y_));
1069 minimise_least_squares (&dydx, &y, ideals);
1072 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
1073 pos = Interval (y, (y+dy));
1076 me->set_grob_property ("positions", ly_interval2scm (pos));
1078 return SCM_UNSPECIFIED;
1083 We can't combine with previous function, since check concave and
1084 slope damping comes first.
1086 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
1088 Beam::shift_region_to_valid (SCM grob)
1090 Grob *me = unsmob_grob (grob);
1094 Array<Real> x_posns ;
1095 Link_array<Item> stems=
1096 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1097 Grob *common = stems[0];
1098 for (int i=1; i < stems.size (); i++)
1099 common = stems[i]->common_refpoint (common, X_AXIS);
1101 Grob *fvs = first_visible_stem (me);
1104 return SCM_UNSPECIFIED;
1106 Real x0 =fvs->relative_coordinate (common, X_AXIS);
1107 for (int i=0; i < stems.size (); i++)
1111 Real x = s->relative_coordinate (common, X_AXIS) - x0;
1115 Grob *lvs = last_visible_stem (me);
1117 return SCM_UNSPECIFIED;
1119 Real dx = lvs->relative_coordinate (common, X_AXIS) - x0;
1121 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
1122 Real dy = pos.delta();
1128 Shift the positions so that we have a chance of finding good
1129 quants (i.e. no short stem failures.)
1131 Interval feasible_left_point;
1132 feasible_left_point.set_full ();
1133 for (int i=0; i < stems.size (); i++)
1136 if (Stem::invisible_b (s))
1140 Direction d = Stem::get_direction (s);
1143 Real left_y = Stem::calc_stem_info (s).shortest_y_
1144 - dydx * x_posns [i];
1150 feasible_left_point.intersect (flp);
1153 if (feasible_left_point.empty_b())
1155 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
1157 else if (!feasible_left_point.elem_b(y))
1159 if (isinf (feasible_left_point[DOWN]))
1160 y = feasible_left_point[UP] - REGION_SIZE;
1161 else if (isinf (feasible_left_point[UP]))
1162 y = feasible_left_point[DOWN]+ REGION_SIZE;
1164 y = feasible_left_point.center ();
1166 pos = Interval (y, (y+dy));
1167 me->set_grob_property ("positions", ly_interval2scm (pos));
1168 return SCM_UNSPECIFIED;
1172 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
1174 Beam::check_concave (SCM smob)
1176 Grob *me = unsmob_grob (smob);
1178 Link_array<Item> stems =
1179 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1181 for (int i = 0; i < stems.size ();)
1183 if (Stem::invisible_b (stems[i]))
1189 if (stems.size () < 3)
1190 return SCM_UNSPECIFIED;
1193 /* Concaveness #1: If distance of an inner notehead to line between
1194 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1195 beam is concave (Heinz Stolba).
1197 In the case of knees, the line connecting outer heads is often
1198 not related to the beam slope (it may even go in the other
1199 direction). Skip the check when the outer stems point in
1200 different directions. --hwn
1203 bool concaveness1 = false;
1204 SCM gap = me->get_grob_property ("concaveness-gap");
1205 if (gh_number_p (gap)
1206 && Stem::get_direction(stems.top ())
1207 == Stem::get_direction(stems[0]))
1209 Real r1 = gh_scm2double (gap);
1210 Real dy = Stem::chord_start_y (stems.top ())
1211 - Stem::chord_start_y (stems[0]);
1214 Real slope = dy / (stems.size () - 1);
1216 Real y0 = Stem::chord_start_y (stems[0]);
1217 for (int i = 1; i < stems.size () - 1; i++)
1219 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1222 concaveness1 = true;
1229 /* Concaveness #2: Sum distances of inner noteheads that fall
1230 outside the interval of the two outer noteheads.
1232 We only do this for beams where first and last stem have the same
1236 Note that "convex" stems compensate for "concave" stems.
1237 (is that intentional?) --hwn.
1240 Real concaveness2 = 0;
1241 SCM thresh = me->get_grob_property ("concaveness-threshold");
1242 Real r2 = infinity_f;
1243 if (!concaveness1 && gh_number_p (thresh)
1244 && Stem::get_direction(stems.top ())
1245 == Stem::get_direction(stems[0]))
1247 r2 = gh_scm2double (thresh);
1249 Direction dir = Stem::get_direction(stems.top ());
1251 Interval iv (Stem::chord_start_y (stems[0]),
1252 Stem::chord_start_y (stems.top ()));
1254 if (iv[MAX] < iv[MIN])
1257 for (int i = 1; i < stems.size () - 1; i++)
1259 Real f = Stem::chord_start_y (stems[i]);
1260 concave += ((f - iv[MAX] ) >? 0) +
1261 ((f - iv[MIN] ) <? 0);
1264 concaveness2 = concave / (stems.size () - 2);
1266 /* ugh: this is the a kludge to get
1267 input/regression/beam-concave.ly to behave as
1271 huh? we're dividing twice (which is not scalable) meaning that
1272 the longer the beam, the more unlikely it will be
1273 concave. Maybe you would even expect the other way around??
1278 concaveness2 /= (stems.size () - 2);
1281 /* TODO: some sort of damping iso -> plain horizontal */
1282 if (concaveness1 || concaveness2 > r2)
1284 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1285 Real r = pos.linear_combination (0);
1286 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1287 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1290 return SCM_UNSPECIFIED;
1293 /* This neat trick is by Werner Lemberg,
1294 damped = tanh (slope)
1295 corresponds with some tables in [Wanske] CHECKME */
1296 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1298 Beam::slope_damping (SCM smob)
1300 Grob *me = unsmob_grob (smob);
1302 if (visible_stem_count (me) <= 1)
1303 return SCM_UNSPECIFIED;
1305 SCM s = me->get_grob_property ("damping");
1306 int damping = gh_scm2int (s);
1310 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1311 Real dy = pos.delta ();
1313 // ugh -> use commonx
1314 Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
1315 - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1316 Real dydx = dy && dx ? dy/dx : 0;
1317 dydx = 0.6 * tanh (dydx) / damping;
1319 Real damped_dy = dydx * dx;
1320 pos[LEFT] += (dy - damped_dy) / 2;
1321 pos[RIGHT] -= (dy - damped_dy) / 2;
1323 me->set_grob_property ("positions", ly_interval2scm (pos));
1325 return SCM_UNSPECIFIED;
1329 where_are_the_whole_beams(SCM beaming)
1333 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1335 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1337 l.add_point (gh_scm2int (gh_car (s)));
1344 Calculate the Y position of the stem-end, given the Y-left, Y-right
1345 in POS, and for stem S.
1348 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos, bool french)
1350 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1351 Real beam_space = get_beam_space (me);
1353 // ugh -> use commonx
1354 Grob * fvs = first_visible_stem (me);
1355 Grob *lvs = last_visible_stem (me);
1357 Real x0 = fvs ? fvs->relative_coordinate (0, X_AXIS) : 0.0;
1358 Real dx = fvs ? lvs->relative_coordinate (0, X_AXIS) - x0 : 0.0;
1359 Real r = s->relative_coordinate (0, X_AXIS) - x0;
1360 Real dy = pos.delta ();
1361 Real stem_y_beam0 = (dy && dx
1368 Direction my_dir = Directional_element_interface::get (s);
1369 SCM beaming = s->get_grob_property ("beaming");
1371 Real stem_y = stem_y_beam0;
1374 Slice bm = where_are_the_whole_beams (beaming);
1376 stem_y += beam_space * bm[-my_dir];
1380 Slice bm = Stem::beam_multiplicity(s);
1382 stem_y +=bm[my_dir] * beam_space;
1389 Hmm. At this time, beam position and slope are determined. Maybe,
1390 stem directions and length should set to relative to the chord's
1391 position of the beam. */
1393 Beam::set_stem_lengths (Grob *me)
1395 Link_array<Item> stems=
1396 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1398 if (stems.size () <= 1)
1401 Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1402 for (int i=1; i < stems.size (); i++)
1403 if (!Stem::invisible_b (stems[i]))
1404 common = common->common_refpoint (stems[i], Y_AXIS);
1406 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1407 Real staff_space = Staff_symbol_referencer::staff_space (me);
1409 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1411 for (int i=0; i < stems.size (); i++)
1414 if (Stem::invisible_b (s))
1417 Real stem_y = calc_stem_y (me, s, pos, french && i > 0&& (i < stems.size () -1));
1419 /* caution: stem measures in staff-positions */
1420 Real id = me->relative_coordinate (common, Y_AXIS)
1421 - stems[i]->relative_coordinate (common, Y_AXIS);
1422 Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1427 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1429 Link_array<Grob> stems=
1430 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1433 for (int i=0; i < stems.size (); i++)
1436 Don't overwrite user settings.
1441 /* Don't set beaming for outside of outer stems */
1442 if ((d == LEFT && i == 0)
1443 ||(d == RIGHT && i == stems.size () -1))
1447 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1448 if (beaming_prop == SCM_EOL ||
1449 index_get_cell (beaming_prop, d) == SCM_EOL)
1451 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1452 Stem::set_beaming (stems[i], b, d);
1455 while (flip (&d) != LEFT);
1460 Beam::forced_stem_count (Grob *me)
1462 Link_array<Item>stems =
1463 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1465 for (int i=0; i < stems.size (); i++)
1469 if (Stem::invisible_b (s))
1472 if (((int)Stem::chord_start_y (s))
1473 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1483 Beam::visible_stem_count (Grob *me)
1485 Link_array<Item>stems =
1486 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1488 for (int i = stems.size (); i--;)
1490 if (!Stem::invisible_b (stems[i]))
1497 Beam::first_visible_stem (Grob *me)
1499 Link_array<Item>stems =
1500 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1502 for (int i = 0; i < stems.size (); i++)
1504 if (!Stem::invisible_b (stems[i]))
1511 Beam::last_visible_stem (Grob *me)
1513 Link_array<Item>stems =
1514 Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1515 for (int i = stems.size (); i--;)
1517 if (!Stem::invisible_b (stems[i]))
1527 handle rest under beam (do_post: beams are calculated now)
1528 what about combination of collisions and rest under beam.
1532 rest -> stem -> beam -> interpolate_y_position ()
1534 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1536 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1538 Grob *rest = unsmob_grob (element_smob);
1539 Axis a = (Axis) gh_scm2int (axis);
1541 assert (a == Y_AXIS);
1543 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1546 return gh_double2scm (0.0);
1547 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1549 || !Beam::has_interface (beam)
1550 || !Beam::visible_stem_count (beam))
1551 return gh_double2scm (0.0);
1553 // make callback for rest from this.
1554 // todo: make sure this calced already.
1556 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1557 Interval pos (0, 0);
1558 SCM s = beam->get_grob_property ("positions");
1559 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1560 pos = ly_scm2interval (s);
1562 Real dy = pos.delta ();
1563 // ugh -> use commonx
1564 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1565 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1566 Real dydx = dy && dx ? dy/dx : 0;
1568 Direction d = Stem::get_direction (stem);
1569 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1571 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1574 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1577 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1579 minimum_dist + -d * (beamy - rest_dim) >? 0;
1581 int stafflines = Staff_symbol_referencer::line_count (rest);
1583 // move discretely by half spaces.
1584 int discrete_dist = int (ceil (dist));
1586 // move by whole spaces inside the staff.
1587 if (discrete_dist < stafflines+1)
1588 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1590 return gh_double2scm (-d * discrete_dist);
1596 ADD_INTERFACE (Beam, "beam-interface",
1599 #'thickness= weight of beams, in staffspace
1602 We take the least squares line through the ideal-length stems, and
1603 then damp that using
1605 damped = tanh (slope)
1607 this gives an unquantized left and right position for the beam end.
1608 Then we take all combinations of quantings near these left and right
1609 positions, and give them a score (according to how close they are to
1610 the ideal slope, how close the result is to the ideal stems, etc.). We
1611 take the best scoring combination.
1614 "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");