2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
17 * Use Number_pair i.s.o Interval to represent (yl, yr).
19 - Determine auto knees based on positions if it's set by the user.
25 - Stems run to the Y-center of the beam.
27 - beam_translation is the offset between Y centers of the beam.
32 #include <math.h> // tanh.
34 #include "molecule.hh"
35 #include "directional-element-interface.hh"
39 #include "least-squares.hh"
41 #include "paper-def.hh"
43 #include "group-interface.hh"
44 #include "staff-symbol-referencer.hh"
50 #define DEBUG_QUANTING 0
54 #include "text-item.hh" // debug output.
55 #include "font-interface.hh" // debug output.
60 Beam::add_stem (Grob *me, Grob *s)
62 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
64 s->add_dependency (me);
66 assert (!Stem::beam_l (s));
67 s->set_grob_property ("beam", me->self_scm ());
69 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
74 this returns the translation between 2 adjoining beams.
77 Beam::get_beam_translation (Grob *me)
79 SCM func = me->get_grob_property ("space-function");
80 SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_beam_count (me)));
81 return gh_scm2double (s);
88 Beam::get_beam_count (Grob *me)
91 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
93 Grob *sc = unsmob_grob (ly_car (s));
95 m = m >? (Stem::beam_multiplicity (sc).length () + 1);
100 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
102 Beam::space_function (SCM smob, SCM beam_count)
104 Grob *me = unsmob_grob (smob);
106 Real staff_space = Staff_symbol_referencer::staff_space (me);
107 Real line = me->paper_l ()->get_var ("linethickness");
108 Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
111 Real beam_translation = gh_scm2int (beam_count) < 4
112 ? (2*staff_space + line - thickness) / 2.0
113 : (3*staff_space + line - thickness) / 3.0;
115 return gh_double2scm (beam_translation);
119 /* After pre-processing all directions should be set.
120 Several post-processing routines (stem, slur, script) need stem/beam
122 Currenly, this means that beam has set all stem's directions.
123 [Alternatively, stems could set its own directions, according to
124 their beam, during 'final-pre-processing'.] */
125 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
127 Beam::before_line_breaking (SCM smob)
129 Grob *me = unsmob_grob (smob);
131 /* Beams with less than 2 two stems don't make much sense, but could happen
136 For a beam that only has one stem, we try to do some disappearance magic:
137 we revert the flag, and move on to The Eternal Engraving Fields. */
139 int count = visible_stem_count (me);
142 me->warning (_ ("beam has less than two visible stems"));
144 SCM stems = me->get_grob_property ("stems");
145 if (scm_ilength (stems) == 1)
147 me->warning (_ ("Beam has less than two stems. Removing beam."));
149 unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
152 return SCM_UNSPECIFIED;
154 else if (scm_ilength (stems) == 0)
157 return SCM_UNSPECIFIED;
162 Direction d = get_default_dir (me);
164 consider_auto_knees (me, d);
165 set_stem_directions (me, d);
169 set_stem_shorten (me);
178 Beam::connect_beams (Grob *me)
180 Link_array<Grob> stems=
181 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
184 last_int.set_empty();
185 for (int i = 0; i< stems.size(); i++)
187 Grob *this_stem = stems[i];
188 SCM this_beaming = this_stem->get_grob_property ("beaming");
190 Direction this_dir = Directional_element_interface::get(this_stem);
193 int start_point = last_int [this_dir];
199 if (d == RIGHT && i == stems.size()-1)
202 new_slice.set_empty();
203 SCM s = index_get_cell (this_beaming, d);
204 for (; gh_pair_p (s); s = gh_cdr (s))
207 start_point - this_dir * gh_scm2int (gh_car (s));
209 new_slice.add_point (new_beam_pos);
210 gh_set_car_x (s, gh_int2scm (new_beam_pos));
213 while (flip (&d) != LEFT);
215 if (!new_slice.empty_b())
216 last_int = new_slice;
220 gh_set_car_x ( this_beaming, SCM_EOL);
221 SCM s = gh_cdr (this_beaming);
222 for (; gh_pair_p (s); s = gh_cdr (s))
224 int np = - this_dir * gh_scm2int (gh_car(s));
225 gh_set_car_x (s, gh_int2scm (np));
226 last_int.add_point (np);
230 if (i == stems.size () -1)
232 gh_set_cdr_x ( this_beaming, SCM_EOL);
237 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
239 Beam::brew_molecule (SCM grob)
241 Grob *me = unsmob_grob (grob);
242 Link_array<Grob> stems=
243 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
244 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
247 if (visible_stem_count (me))
249 // ugh -> use commonx
250 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
251 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
255 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
256 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
259 SCM posns = me->get_grob_property ("positions");
261 if (!ly_number_pair_p (posns))
263 programming_error ("No beam posns");
264 pos = Interval (0,0);
267 pos= ly_scm2interval (posns);
269 Real dy = pos.delta ();
270 Real dydx = dy && dx ? dy/dx : 0;
272 Real thick = gh_scm2double (me->get_grob_property ("thickness"));
273 Real bdy = get_beam_translation (me);
275 SCM last_beaming = SCM_EOL;;
276 Real last_xposn = -1;
277 Real last_width = -1 ;
280 SCM gap = me->get_grob_property ("gap");
282 Real lt = me->paper_l ()->get_var ("linethickness");
283 for (int i = 0; i< stems.size(); i++)
287 SCM this_beaming = st->get_grob_property ("beaming");
288 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
289 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
293 SCM left = gh_cdr (last_beaming);
294 SCM right = gh_car (this_beaming);
296 Array<int> fullbeams;
297 Array<int> lfliebertjes;
298 Array<int> rfliebertjes;
301 gh_pair_p (s); s =gh_cdr (s))
303 int b = gh_scm2int (gh_car (s));
304 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
310 lfliebertjes.push (b);
314 gh_pair_p (s); s =gh_cdr (s))
316 int b = gh_scm2int (gh_car (s));
317 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
319 rfliebertjes.push (b);
324 Real w = xposn - last_xposn;
325 Real stem_offset = 0.0;
326 Real width_corr = 0.0;
329 stem_offset -= last_width/2;
330 width_corr += last_width/2;
333 if (i == stems.size() -1)
335 width_corr += stem_width/2;
338 if (gh_number_p (gap))
340 Real g = gh_scm2double (gap);
345 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
346 for (int j = fullbeams.size(); j--;)
349 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
350 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
351 the_beam.add_molecule (b);
354 if (lfliebertjes.size() || rfliebertjes.size())
358 if (!Stem::first_head (st))
362 int t = Stem::duration_log (st);
364 SCM proc = me->get_grob_property ("flag-width-function");
365 SCM result = gh_call1 (proc, gh_int2scm (t));
366 nw_f = gh_scm2double (result);
369 /* Half beam should be one note-width,
370 but let's make sure two half-beams never touch */
372 Real w = xposn - last_xposn;
375 Molecule half = Lookup::beam (dydx, w, thick);
376 for (int j = lfliebertjes.size(); j--;)
379 b.translate_axis (last_xposn - x0, X_AXIS);
380 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
381 the_beam.add_molecule (b);
383 for (int j = rfliebertjes.size(); j--;)
386 b.translate_axis (xposn - x0 - w , X_AXIS);
387 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
388 the_beam.add_molecule (b);
394 last_width = stem_width;
395 last_beaming = this_beaming;
398 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
399 the_beam.translate_axis (pos[LEFT], Y_AXIS);
404 This code prints the demerits for each beam. Perhaps this
405 should be switchable for those who want to twiddle with the
411 str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
414 str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
417 SCM properties = Font_interface::font_alist_chain (me);
420 Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
421 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0);
427 return the_beam.smobbed_copy();
434 Beam::get_default_dir (Grob *me)
436 Drul_array<int> total;
437 total[UP] = total[DOWN] = 0;
438 Drul_array<int> count;
439 count[UP] = count[DOWN] = 0;
442 Link_array<Item> stems=
443 Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
445 for (int i=0; i <stems.size (); i++)
448 Direction sd = Directional_element_interface::get (s);
450 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
451 int current = sd ? (1 + d * sd)/2 : center_distance;
458 } while (flip (&d) != DOWN);
460 SCM func = me->get_grob_property ("dir-function");
461 SCM s = gh_call2 (func,
462 gh_cons (gh_int2scm (count[UP]),
463 gh_int2scm (count[DOWN])),
464 gh_cons (gh_int2scm (total[UP]),
465 gh_int2scm (total[DOWN])));
467 if (gh_number_p (s) && gh_scm2int (s))
470 /* If dir is not determined: get default */
471 return to_dir (me->get_grob_property ("neutral-direction"));
475 /* Set all stems with non-forced direction to beam direction.
476 Urg: non-forced should become `without/with unforced' direction,
477 once stem gets cleaned-up. */
479 Beam::set_stem_directions (Grob *me, Direction d)
481 Link_array<Item> stems
482 =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
484 for (int i=0; i <stems.size (); i++)
487 SCM force = s->remove_grob_property ("dir-forced");
488 if (!gh_boolean_p (force) || !gh_scm2bool (force))
489 Directional_element_interface::set (s, d);
493 /* Simplistic auto-knees; only consider vertical gap between two
496 `Forced' stem directions are ignored. If you don't want auto-knees,
497 don't set, or unset auto-knee-gap. */
499 Beam::consider_auto_knees (Grob *me, Direction d)
501 SCM scm = me->get_grob_property ("auto-knee-gap");
503 if (gh_number_p (scm))
507 Real staff_space = Staff_symbol_referencer::staff_space (me);
508 Real gap = gh_scm2double (scm) / staff_space;
510 Link_array<Grob> stems=
511 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
513 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
516 for (int i=1; i < stems.size (); i++)
518 if (!Stem::invisible_b (stems[i-1]))
520 if (Stem::invisible_b (stems[l]))
522 if (Stem::invisible_b (stems[i]))
525 Real left = Stem::extremal_heads (stems[l])[d]
526 ->relative_coordinate (common, Y_AXIS);
527 Real right = Stem::extremal_heads (stems[i])[-d]
528 ->relative_coordinate (common, Y_AXIS);
530 Real dy = right - left;
534 knee_y = (right + left) / 2;
542 for (int i=0; i < stems.size (); i++)
545 if (Stem::invisible_b (s) ||
546 s->get_grob_property ("dir-forced") == SCM_BOOL_T)
548 Real y = Stem::extremal_heads (stems[i])[d]
549 ->relative_coordinate (common, Y_AXIS);
551 Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
552 s->set_grob_property ("dir-forced", SCM_BOOL_T);
558 /* Set stem's shorten property if unset.
561 take some y-position (chord/beam/nearest?) into account
562 scmify forced-fraction
566 why is shorten stored in beam, and not directly in stem?
570 Beam::set_stem_shorten (Grob *m)
572 Spanner*me = dynamic_cast<Spanner*> (m);
574 Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
576 int beam_count = get_beam_count (me);
578 SCM shorten = me->get_grob_property ("beamed-stem-shorten");
579 if (shorten == SCM_EOL)
582 int sz = scm_ilength (shorten);
584 Real staff_space = Staff_symbol_referencer::staff_space (me);
585 SCM shorten_elt = scm_list_ref (shorten,
586 gh_int2scm (beam_count <? (sz - 1)));
587 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
589 /* your similar cute comment here */
590 shorten_f *= forced_fraction;
593 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
596 /* Call list of y-dy-callbacks, that handle setting of
600 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
602 Beam::after_line_breaking (SCM smob)
604 Grob *me = unsmob_grob (smob);
606 /* Copy to mutable list. */
607 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
608 me->set_grob_property ("positions", s);
610 if (ly_car (s) == SCM_BOOL_F)
613 // one wonders if such genericity is necessary --hwn.
614 SCM callbacks = me->get_grob_property ("position-callbacks");
615 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
616 gh_call1 (ly_car (i), smob);
619 set_stem_lengths (me);
620 return SCM_UNSPECIFIED;
623 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
625 Beam::least_squares (SCM smob)
627 Grob *me = unsmob_grob (smob);
629 int count = visible_stem_count (me);
634 me->set_grob_property ("positions", ly_interval2scm (pos));
635 return SCM_UNSPECIFIED;
639 Array<Real> x_posns ;
640 Link_array<Grob> stems=
641 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
642 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
643 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
645 Real my_y = me->relative_coordinate (commony, Y_AXIS);
647 Grob *fvs = first_visible_stem (me);
648 Grob *lvs = last_visible_stem (me);
650 Interval ideal (Stem::calc_stem_info (fvs).ideal_y_
651 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
652 Stem::calc_stem_info (lvs).ideal_y_
653 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
655 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
656 for (int i=0; i < stems.size (); i++)
660 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
663 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
671 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
672 Stem::chord_start_y (last_visible_stem (me)));
676 TODO -- use scoring for this.
678 complicated, because we take stem-info.ideal for determining
681 /* Make simple beam on middle line have small tilt */
682 if (!ideal[LEFT] && chord.delta () && count == 2)
688 Direction d = (Direction) (sign (chord.delta ()) * UP);
689 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
704 Array<Offset> ideals;
705 for (int i=0; i < stems.size (); i++)
708 if (Stem::invisible_b (s))
710 ideals.push (Offset (x_posns[i],
711 Stem::calc_stem_info (s).ideal_y_
712 + s->relative_coordinate (commony, Y_AXIS)
715 minimise_least_squares (&dydx, &y, ideals);
718 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
719 pos = Interval (y, (y+dy));
722 me->set_grob_property ("positions", ly_interval2scm (pos));
724 return SCM_UNSPECIFIED;
729 We can't combine with previous function, since check concave and
730 slope damping comes first.
732 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
734 Beam::shift_region_to_valid (SCM grob)
736 Grob *me = unsmob_grob (grob);
740 Array<Real> x_posns ;
741 Link_array<Grob> stems=
742 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
743 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
744 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
746 Grob *fvs = first_visible_stem (me);
749 return SCM_UNSPECIFIED;
751 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
752 for (int i=0; i < stems.size (); i++)
756 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
760 Grob *lvs = last_visible_stem (me);
762 return SCM_UNSPECIFIED;
764 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
766 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
767 Real dy = pos.delta();
773 Shift the positions so that we have a chance of finding good
774 quants (i.e. no short stem failures.)
776 Interval feasible_left_point;
777 feasible_left_point.set_full ();
778 for (int i=0; i < stems.size (); i++)
781 if (Stem::invisible_b (s))
784 Direction d = Stem::get_direction (s);
787 Stem::calc_stem_info (s).shortest_y_
788 - dydx * x_posns [i];
791 left_y is now relative to the stem S. We want relative to
792 ourselves, so translate:
795 + s->relative_coordinate (commony, Y_AXIS)
796 - me->relative_coordinate (commony, Y_AXIS);
802 feasible_left_point.intersect (flp);
805 if (feasible_left_point.empty_b())
807 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
809 else if (!feasible_left_point.elem_b(y))
811 if (isinf (feasible_left_point[DOWN]))
812 y = feasible_left_point[UP] - REGION_SIZE;
813 else if (isinf (feasible_left_point[UP]))
814 y = feasible_left_point[DOWN]+ REGION_SIZE;
816 y = feasible_left_point.center ();
818 pos = Interval (y, (y+dy));
819 me->set_grob_property ("positions", ly_interval2scm (pos));
820 return SCM_UNSPECIFIED;
824 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
826 Beam::check_concave (SCM smob)
828 Grob *me = unsmob_grob (smob);
830 Link_array<Grob> stems =
831 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
833 for (int i = 0; i < stems.size ();)
835 if (Stem::invisible_b (stems[i]))
841 if (stems.size () < 3)
842 return SCM_UNSPECIFIED;
845 /* Concaveness #1: If distance of an inner notehead to line between
846 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
847 beam is concave (Heinz Stolba).
849 In the case of knees, the line connecting outer heads is often
850 not related to the beam slope (it may even go in the other
851 direction). Skip the check when the outer stems point in
852 different directions. --hwn
855 bool concaveness1 = false;
856 SCM gap = me->get_grob_property ("concaveness-gap");
857 if (gh_number_p (gap)
858 && Stem::get_direction(stems.top ())
859 == Stem::get_direction(stems[0]))
861 Real r1 = gh_scm2double (gap);
862 Real dy = Stem::chord_start_y (stems.top ())
863 - Stem::chord_start_y (stems[0]);
866 Real slope = dy / (stems.size () - 1);
868 Real y0 = Stem::chord_start_y (stems[0]);
869 for (int i = 1; i < stems.size () - 1; i++)
871 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
881 /* Concaveness #2: Sum distances of inner noteheads that fall
882 outside the interval of the two outer noteheads.
884 We only do this for beams where first and last stem have the same
888 Note that "convex" stems compensate for "concave" stems.
889 (is that intentional?) --hwn.
892 Real concaveness2 = 0;
893 SCM thresh = me->get_grob_property ("concaveness-threshold");
894 Real r2 = infinity_f;
895 if (!concaveness1 && gh_number_p (thresh)
896 && Stem::get_direction(stems.top ())
897 == Stem::get_direction(stems[0]))
899 r2 = gh_scm2double (thresh);
901 Direction dir = Stem::get_direction(stems.top ());
903 Interval iv (Stem::chord_start_y (stems[0]),
904 Stem::chord_start_y (stems.top ()));
906 if (iv[MAX] < iv[MIN])
909 for (int i = 1; i < stems.size () - 1; i++)
911 Real f = Stem::chord_start_y (stems[i]);
912 concave += ((f - iv[MAX] ) >? 0) +
913 ((f - iv[MIN] ) <? 0);
916 concaveness2 = concave / (stems.size () - 2);
918 /* ugh: this is the a kludge to get
919 input/regression/beam-concave.ly to behave as
923 huh? we're dividing twice (which is not scalable) meaning that
924 the longer the beam, the more unlikely it will be
925 concave. Maybe you would even expect the other way around??
930 concaveness2 /= (stems.size () - 2);
933 /* TODO: some sort of damping iso -> plain horizontal */
934 if (concaveness1 || concaveness2 > r2)
936 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
937 Real r = pos.linear_combination (0);
938 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
939 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
942 return SCM_UNSPECIFIED;
945 /* This neat trick is by Werner Lemberg,
946 damped = tanh (slope)
947 corresponds with some tables in [Wanske] CHECKME */
948 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
950 Beam::slope_damping (SCM smob)
952 Grob *me = unsmob_grob (smob);
954 if (visible_stem_count (me) <= 1)
955 return SCM_UNSPECIFIED;
957 SCM s = me->get_grob_property ("damping");
958 int damping = gh_scm2int (s);
962 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
963 Real dy = pos.delta ();
965 Grob *fvs = first_visible_stem (me);
966 Grob *lvs = last_visible_stem (me);
968 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
971 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
972 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
973 Real dydx = dy && dx ? dy/dx : 0;
974 dydx = 0.6 * tanh (dydx) / damping;
976 Real damped_dy = dydx * dx;
977 pos[LEFT] += (dy - damped_dy) / 2;
978 pos[RIGHT] -= (dy - damped_dy) / 2;
980 me->set_grob_property ("positions", ly_interval2scm (pos));
982 return SCM_UNSPECIFIED;
986 where_are_the_whole_beams(SCM beaming)
990 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
992 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
994 l.add_point (gh_scm2int (gh_car (s)));
1001 Calculate the Y position of the stem-end, given the Y-left, Y-right
1002 in POS for stem S. This Y position is relative to S.
1005 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1007 Interval pos, bool french)
1009 Real beam_translation = get_beam_translation (me);
1012 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1013 Real dy = pos.delta ();
1015 Real stem_y_beam0 = (dy && dx
1020 Direction my_dir = Directional_element_interface::get (s);
1021 SCM beaming = s->get_grob_property ("beaming");
1023 Real stem_y = stem_y_beam0;
1026 Slice bm = where_are_the_whole_beams (beaming);
1028 stem_y += beam_translation * bm[-my_dir];
1032 Slice bm = Stem::beam_multiplicity(s);
1034 stem_y +=bm[my_dir] * beam_translation;
1037 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1038 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1044 Hmm. At this time, beam position and slope are determined. Maybe,
1045 stem directions and length should set to relative to the chord's
1046 position of the beam. */
1048 Beam::set_stem_lengths (Grob *me)
1050 Link_array<Grob> stems=
1051 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1053 if (stems.size () <= 1)
1057 for (int a = 2; a--;)
1058 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1060 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1061 Real staff_space = Staff_symbol_referencer::staff_space (me);
1063 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1068 if (gh_number_p (me->get_grob_property ("gap"))
1069 &&gh_scm2double (me->get_grob_property ("gap")))
1072 thick = gh_scm2double (me->get_grob_property ("thickness"))
1073 * Staff_symbol_referencer::staff_space(me);
1076 // ugh -> use commonx
1077 Grob * fvs = first_visible_stem (me);
1078 Grob *lvs = last_visible_stem (me);
1080 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1081 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1083 for (int i=0; i < stems.size (); i++)
1086 if (Stem::invisible_b (s))
1089 Real stem_y = calc_stem_y (me, s, common,
1091 pos, french && i > 0&& (i < stems.size () -1));
1094 Make the stems go up to the end of the beam. This doesn't matter
1095 for normal beams, but for tremolo beams it looks silly otherwise.
1098 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1100 Stem::set_stemend (s, 2* stem_y / staff_space);
1105 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1107 Link_array<Grob> stems=
1108 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1111 for (int i=0; i < stems.size (); i++)
1114 Don't overwrite user settings.
1119 /* Don't set beaming for outside of outer stems */
1120 if ((d == LEFT && i == 0)
1121 ||(d == RIGHT && i == stems.size () -1))
1125 SCM beaming_prop = stems[i]->get_grob_property ("beaming");
1126 if (beaming_prop == SCM_EOL ||
1127 index_get_cell (beaming_prop, d) == SCM_EOL)
1129 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1130 Stem::set_beaming (stems[i], b, d);
1133 while (flip (&d) != LEFT);
1138 Beam::forced_stem_count (Grob *me)
1140 Link_array<Grob>stems =
1141 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1143 for (int i=0; i < stems.size (); i++)
1147 if (Stem::invisible_b (s))
1150 if (((int)Stem::chord_start_y (s))
1151 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1161 Beam::visible_stem_count (Grob *me)
1163 Link_array<Grob>stems =
1164 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1166 for (int i = stems.size (); i--;)
1168 if (!Stem::invisible_b (stems[i]))
1175 Beam::first_visible_stem (Grob *me)
1177 Link_array<Grob>stems =
1178 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1180 for (int i = 0; i < stems.size (); i++)
1182 if (!Stem::invisible_b (stems[i]))
1189 Beam::last_visible_stem (Grob *me)
1191 Link_array<Grob>stems =
1192 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1193 for (int i = stems.size (); i--;)
1195 if (!Stem::invisible_b (stems[i]))
1205 handle rest under beam (do_post: beams are calculated now)
1206 what about combination of collisions and rest under beam.
1210 rest -> stem -> beam -> interpolate_y_position ()
1212 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1214 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1216 Grob *rest = unsmob_grob (element_smob);
1217 Axis a = (Axis) gh_scm2int (axis);
1219 assert (a == Y_AXIS);
1221 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1224 return gh_double2scm (0.0);
1225 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1227 || !Beam::has_interface (beam)
1228 || !Beam::visible_stem_count (beam))
1229 return gh_double2scm (0.0);
1231 // make callback for rest from this.
1232 // todo: make sure this calced already.
1234 // Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1235 Interval pos (0, 0);
1236 SCM s = beam->get_grob_property ("positions");
1237 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1238 pos = ly_scm2interval (s);
1240 Real dy = pos.delta ();
1241 // ugh -> use commonx
1242 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1243 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1244 Real dydx = dy && dx ? dy/dx : 0;
1246 Direction d = Stem::get_direction (stem);
1247 Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1249 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1252 Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1255 = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1257 minimum_dist + -d * (beamy - rest_dim) >? 0;
1259 int stafflines = Staff_symbol_referencer::line_count (rest);
1261 // move discretely by half spaces.
1262 int discrete_dist = int (ceil (dist));
1264 // move by whole spaces inside the staff.
1265 if (discrete_dist < stafflines+1)
1266 discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1268 return gh_double2scm (-d * discrete_dist);
1274 ADD_INTERFACE (Beam, "beam-interface",
1277 #'thickness= weight of beams, in staffspace
1280 We take the least squares line through the ideal-length stems, and
1281 then damp that using
1283 damped = tanh (slope)
1285 this gives an unquantized left and right position for the beam end.
1286 Then we take all combinations of quantings near these left and right
1287 positions, and give them a score (according to how close they are to
1288 the ideal slope, how close the result is to the ideal stems, etc.). We
1289 take the best scoring combination.
1292 "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");