2002->2003
[lilypond.git] / lily / beam.cc
blob2f403f306722ad21b4996447596c04cfe99f8752
1 /*
2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2003 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
8 */
11 TODO:
13 * Use Number_pair i.s.o Interval to represent (yl, yr).
15 - Determine auto knees based on positions if it's set by the user.
18 Notes:
21 - Stems run to the Y-center of the beam.
23 - beam_translation is the offset between Y centers of the beam.
28 #include <math.h> // tanh.
30 #include "molecule.hh"
31 #include "directional-element-interface.hh"
32 #include "beaming.hh"
33 #include "beam.hh"
34 #include "misc.hh"
35 #include "least-squares.hh"
36 #include "stem.hh"
37 #include "paper-def.hh"
38 #include "lookup.hh"
39 #include "group-interface.hh"
40 #include "staff-symbol-referencer.hh"
41 #include "item.hh"
42 #include "spanner.hh"
43 #include "warn.hh"
46 #define DEBUG_QUANTING 0
49 #if DEBUG_QUANTING
50 #include "text-item.hh" // debug output.
51 #include "font-interface.hh" // debug output.
52 #endif
55 void
56 Beam::add_stem (Grob *me, Grob *s)
58 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
60 s->add_dependency (me);
62 assert (!Stem::get_beam (s));
63 s->set_grob_property ("beam", me->self_scm ());
65 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
69 Real
70 Beam::get_thickness (Grob * me)
72 SCM th = me->get_grob_property ("thickness");
73 if (gh_number_p (th))
74 return gh_scm2double (th)* Staff_symbol_referencer::staff_space (me);
75 else
76 return 0.0;
79 /* Return the translation between 2 adjoining beams. */
80 Real
81 Beam::get_beam_translation (Grob *me)
83 SCM func = me->get_grob_property ("space-function");
84 SCM s = gh_call2 (func, me->self_scm (), scm_int2num (get_beam_count (me)));
85 return gh_scm2double (s);
88 /* Maximum beam_count. */
89 int
90 Beam::get_beam_count (Grob *me)
92 int m = 0;
93 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
95 Grob *stem = unsmob_grob (ly_car (s));
96 m = m >? (Stem::beam_multiplicity (stem).length () + 1);
98 return m;
103 Space return space between beams.
105 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
107 Beam::space_function (SCM smob, SCM beam_count)
109 Grob *me = unsmob_grob (smob);
111 Real staff_space = Staff_symbol_referencer::staff_space (me);
112 Real line = me->get_paper ()->get_var ("linethickness");
113 Real thickness = get_thickness (me);
115 Real beam_translation = gh_scm2int (beam_count) < 4
116 ? (2*staff_space + line - thickness) / 2.0
117 : (3*staff_space + line - thickness) / 3.0;
119 return gh_double2scm (beam_translation);
123 /* After pre-processing all directions should be set.
124 Several post-processing routines (stem, slur, script) need stem/beam
125 direction.
126 Currenly, this means that beam has set all stem's directions.
127 [Alternatively, stems could set its own directions, according to
128 their beam, during 'final-pre-processing'.] */
129 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
131 Beam::before_line_breaking (SCM smob)
133 Grob *me = unsmob_grob (smob);
135 /* Beams with less than 2 two stems don't make much sense, but could happen
136 when you do
138 [r8 c8 r8].
140 For a beam that only has one stem, we try to do some disappearance magic:
141 we revert the flag, and move on to The Eternal Engraving Fields. */
143 int count = visible_stem_count (me);
144 if (count < 2)
146 me->warning (_ ("beam has less than two visible stems"));
148 SCM stems = me->get_grob_property ("stems");
149 if (scm_ilength (stems) == 1)
151 me->warning (_ ("Beam has less than two stems. Removing beam."));
153 unsmob_grob (gh_car (stems))->set_grob_property ("beam", SCM_EOL);
154 me->suicide ();
156 return SCM_UNSPECIFIED;
158 else if (scm_ilength (stems) == 0)
160 me->suicide ();
161 return SCM_UNSPECIFIED;
164 if (count >= 1)
166 Direction d = get_default_dir (me);
168 consider_auto_knees (me);
169 set_stem_directions (me, d);
171 connect_beams (me);
173 set_stem_shorten (me);
176 return SCM_EOL;
181 We want a maximal number of shared beams, but if there is choice, we
182 take the one that is closest to the end of the stem. This is for situations like
187 |===|
195 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
196 Direction left_dir,
197 Direction right_dir)
199 Slice lslice = int_list_to_slice (gh_cdr (left_beaming));
201 int best_count = 0;
202 int best_start = 0;
203 for (int i = lslice[-left_dir];
204 (i - lslice[left_dir])* left_dir <= 0 ; i+= left_dir)
206 int count =0;
207 for ( SCM s = gh_car (right_beaming); gh_pair_p (s); s = gh_cdr (s))
209 int k = - right_dir * gh_scm2int (gh_car (s)) + i;
210 if (scm_memq (scm_int2num (k), left_beaming) != SCM_BOOL_F)
211 count ++;
214 if (count >= best_count)
216 best_count = count;
217 best_start = i;
221 return best_start;
224 void
225 Beam::connect_beams (Grob *me)
227 Link_array<Grob> stems=
228 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
230 Slice last_int;
231 last_int.set_empty();
232 SCM last_beaming = SCM_EOL;
233 Direction last_dir = CENTER;
234 for (int i = 0; i< stems.size(); i++)
236 Grob *this_stem = stems[i];
237 SCM this_beaming = this_stem->get_grob_property ("beaming");
239 Direction this_dir = Directional_element_interface::get(this_stem);
240 if (gh_pair_p (last_beaming) && gh_pair_p (this_beaming))
242 int start_point = position_with_maximal_common_beams
243 (last_beaming, this_beaming,
244 last_dir, this_dir);
246 Direction d = LEFT;
247 Slice new_slice ;
250 if (d == RIGHT && i == stems.size()-1)
251 continue;
253 new_slice.set_empty();
254 SCM s = index_get_cell (this_beaming, d);
255 for (; gh_pair_p (s); s = gh_cdr (s))
257 int new_beam_pos =
258 start_point - this_dir * gh_scm2int (gh_car (s));
260 new_slice.add_point (new_beam_pos);
261 gh_set_car_x (s, scm_int2num (new_beam_pos));
266 while (flip (&d) != LEFT);
268 if (!new_slice.empty_b())
269 last_int = new_slice;
271 else
273 gh_set_car_x ( this_beaming, SCM_EOL);
274 SCM s = gh_cdr (this_beaming);
275 for (; gh_pair_p (s); s = gh_cdr (s))
277 int np = - this_dir * gh_scm2int (gh_car(s));
278 gh_set_car_x (s, scm_int2num (np));
279 last_int.add_point (np);
283 if (i == stems.size () -1)
285 gh_set_cdr_x (this_beaming, SCM_EOL);
288 if (scm_ilength (gh_cdr (this_beaming)) > 0)
290 last_beaming = this_beaming;
291 last_dir = this_dir;
296 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
298 Beam::brew_molecule (SCM grob)
300 Grob *me = unsmob_grob (grob);
301 Link_array<Grob> stems=
302 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
303 Grob* xcommon = common_refpoint_of_array (stems, me, X_AXIS);
305 Real x0, dx;
306 if (visible_stem_count (me))
308 // ugh -> use commonx
309 x0 = first_visible_stem (me)->relative_coordinate (xcommon, X_AXIS);
310 dx = last_visible_stem (me)->relative_coordinate (xcommon, X_AXIS) - x0;
312 else
314 x0 = stems[0]->relative_coordinate (xcommon, X_AXIS);
315 dx = stems.top ()->relative_coordinate (xcommon, X_AXIS) - x0;
318 SCM posns = me->get_grob_property ("positions");
319 Interval pos;
320 if (!ly_number_pair_p (posns))
322 programming_error ("No beam posns");
323 pos = Interval (0,0);
325 else
326 pos= ly_scm2interval (posns);
328 Real dy = pos.delta ();
329 Real dydx = dy && dx ? dy/dx : 0;
331 Real thick = get_thickness (me);
332 Real bdy = get_beam_translation (me);
334 SCM last_beaming = SCM_EOL;;
335 Real last_xposn = -1;
336 Real last_width = -1 ;
339 SCM gap = me->get_grob_property ("gap");
340 Molecule the_beam;
341 Real lt = me->get_paper ()->get_var ("linethickness");
343 for (int i = 0; i< stems.size(); i++)
345 Grob * st =stems[i];
347 SCM this_beaming = st->get_grob_property ("beaming");
348 Real xposn = st->relative_coordinate (xcommon, X_AXIS);
349 Real stem_width = gh_scm2double (st->get_grob_property ("thickness")) *lt;
352 We do the space left of ST, with lfliebertjes pointing to the
353 right from the left stem, and rfliebertjes pointing left from
354 right stem.
356 if (i > 0)
358 SCM left = gh_cdr (last_beaming);
359 SCM right = gh_car (this_beaming);
361 Array<int> fullbeams;
362 Array<int> lfliebertjes;
363 Array<int> rfliebertjes;
365 for (SCM s = left;
366 gh_pair_p (s); s =gh_cdr (s))
368 int b = gh_scm2int (gh_car (s));
369 if (scm_memq (gh_car(s), right) != SCM_BOOL_F)
371 fullbeams.push (b);
373 else
375 lfliebertjes.push (b);
378 for (SCM s = right;
379 gh_pair_p (s); s =gh_cdr (s))
381 int b = gh_scm2int (gh_car (s));
382 if (scm_memq (gh_car(s), left) == SCM_BOOL_F)
384 rfliebertjes.push (b);
389 Real w = xposn - last_xposn;
390 Real stem_offset = 0.0;
391 Real width_corr = 0.0;
392 if (i == 1)
394 stem_offset -= last_width/2;
395 width_corr += last_width/2;
398 if (i == stems.size() -1)
400 width_corr += stem_width/2;
403 if (gh_number_p (gap))
405 Real g = gh_scm2double (gap);
406 stem_offset += g;
407 width_corr -= 2*g;
410 Molecule whole = Lookup::beam (dydx, w + width_corr, thick);
411 for (int j = fullbeams.size(); j--;)
413 Molecule b (whole);
414 b.translate_axis (last_xposn - x0 + stem_offset, X_AXIS);
415 b.translate_axis (dydx * (last_xposn - x0) + bdy * fullbeams[j], Y_AXIS);
416 the_beam.add_molecule (b);
419 if (lfliebertjes.size() || rfliebertjes.size())
421 Real nw_f;
423 int t = Stem::duration_log (st);
425 SCM proc = me->get_grob_property ("flag-width-function");
426 SCM result = gh_call1 (proc, scm_int2num (t));
427 nw_f = gh_scm2double (result);
430 /* Half beam should be one note-width,
431 but let's make sure two half-beams never touch */
433 Real w = xposn - last_xposn;
434 w = w/2 <? nw_f;
436 Molecule half = Lookup::beam (dydx, w, thick);
437 for (int j = lfliebertjes.size(); j--;)
439 Molecule b (half);
440 b.translate_axis (last_xposn - x0, X_AXIS);
441 b.translate_axis (dydx * (last_xposn-x0) + bdy * lfliebertjes[j], Y_AXIS);
442 the_beam.add_molecule (b);
444 for (int j = rfliebertjes.size(); j--;)
446 Molecule b (half);
447 b.translate_axis (xposn - x0 - w , X_AXIS);
448 b.translate_axis (dydx * (xposn-x0 -w) + bdy * rfliebertjes[j], Y_AXIS);
449 the_beam.add_molecule (b);
454 last_xposn = xposn;
455 last_width = stem_width;
456 last_beaming = this_beaming;
459 the_beam.translate_axis (x0 - me->relative_coordinate (xcommon, X_AXIS), X_AXIS);
460 the_beam.translate_axis (pos[LEFT], Y_AXIS);
462 #if (DEBUG_QUANTING)
465 This code prints the demerits for each beam. Perhaps this
466 should be switchable for those who want to twiddle with the
467 parameters.
469 String str;
470 if (1)
472 str += to_string (gh_scm2int (me->get_grob_property ("best-idx")));
473 str += ":";
475 str += to_string (gh_scm2double (me->get_grob_property ("quant-score")),
476 "%.2f");
478 SCM properties = Font_interface::font_alist_chain (me);
481 Molecule tm = Text_item::text2molecule (me, scm_makfrom0str (str.to_str0 ()), properties);
482 the_beam.add_at_edge (Y_AXIS, UP, tm, 5.0, 0);
484 #endif
488 return the_beam.smobbed_copy();
494 Direction
495 Beam::get_default_dir (Grob *me)
497 Drul_array<int> total;
498 total[UP] = total[DOWN] = 0;
499 Drul_array<int> count;
500 count[UP] = count[DOWN] = 0;
501 Direction d = DOWN;
503 Link_array<Grob> stems=
504 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
506 for (int i=0; i <stems.size (); i++)
507 do {
508 Grob *s = stems[i];
509 Direction sd = Directional_element_interface::get (s);
511 int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
512 int current = sd ? (1 + d * sd)/2 : center_distance;
514 if (current)
516 total[d] += current;
517 count[d] ++;
519 } while (flip (&d) != DOWN);
521 SCM func = me->get_grob_property ("dir-function");
522 SCM s = gh_call2 (func,
523 gh_cons (scm_int2num (count[UP]),
524 scm_int2num (count[DOWN])),
525 gh_cons (scm_int2num (total[UP]),
526 scm_int2num (total[DOWN])));
528 if (gh_number_p (s) && gh_scm2int (s))
529 return to_dir (s);
531 /* If dir is not determined: get default */
532 return to_dir (me->get_grob_property ("neutral-direction"));
536 /* Set all stems with non-forced direction to beam direction.
537 Urg: non-forced should become `without/with unforced' direction,
538 once stem gets cleaned-up. */
539 void
540 Beam::set_stem_directions (Grob *me, Direction d)
542 Link_array<Grob> stems
543 =Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
545 for (int i=0; i <stems.size (); i++)
547 Grob *s = stems[i];
549 SCM forcedir = s->get_grob_property ("direction");
550 if (!to_dir (forcedir))
551 Directional_element_interface::set (s, d);
556 A union of intervals in the real line.
558 Abysmal performance (quadratic) for large N, hopefully we don't have
559 that large N. In any case, this should probably be rewritten to use
560 a balanced tree.
562 struct Int_set
564 Array<Interval> allowed_regions_;
566 Int_set()
568 set_full();
571 void set_full()
573 allowed_regions_.clear();
574 Interval s;
575 s.set_full ();
576 allowed_regions_.push (s);
579 void remove_interval (Interval rm)
581 for (int i = 0; i < allowed_regions_.size(); )
583 Interval s = rm;
585 s.intersect (allowed_regions_[i]);
587 if (!s.empty_b ())
589 Interval before = allowed_regions_[i];
590 Interval after = allowed_regions_[i];
592 before[RIGHT] = s[LEFT];
593 after[LEFT] = s[RIGHT];
595 if (!before.empty_b() && before.length () > 0.0)
597 allowed_regions_.insert (before, i);
598 i++;
600 allowed_regions_.del (i);
601 if (!after.empty_b () && after.length () > 0.0)
603 allowed_regions_.insert (after, i);
604 i++;
607 else
608 i++;
615 Only try horizontal beams for knees. No reliable detection of
616 anything else is possible here, since we don't know funky-beaming
617 settings, or X-distances (slopes!) People that want sloped
618 knee-beams, should set the directions manually.
620 void
621 Beam::consider_auto_knees (Grob* me)
623 SCM scm = me->get_grob_property ("auto-knee-gap");
624 if (!gh_number_p (scm))
625 return ;
627 Real threshold = gh_scm2double (scm);
629 Int_set gaps;
631 gaps.set_full ();
633 Link_array<Grob> stems=
634 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
636 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
637 Real staff_space = Staff_symbol_referencer::staff_space (me);
639 Array<Interval> hps_array;
640 for (int i=0; i < stems.size (); i++)
642 Grob* stem = stems[i];
643 if (Stem::invisible_b (stem))
644 continue;
646 Interval hps = Stem::head_positions (stem);
647 if(!hps.empty_b())
649 hps[LEFT] += -1;
650 hps[RIGHT] += 1;
651 hps *= staff_space * 0.5 ;
654 We could subtract beam Y position, but this routine only
655 sets stem directions, a constant shift does not have an
656 influence.
659 hps += stem->relative_coordinate (common, Y_AXIS);
661 if (to_dir (stem->get_grob_property ("direction")))
663 Direction stemdir = to_dir (stem->get_grob_property ("direction"));
664 hps[-stemdir] = - stemdir * infinity_f;
667 hps_array.push (hps);
669 gaps.remove_interval (hps);
672 Interval max_gap;
673 Real max_gap_len =0.0;
675 for (int i = gaps.allowed_regions_.size() -1; i >= 0 ; i--)
677 Interval gap = gaps.allowed_regions_[i];
680 the outer gaps are not knees.
682 if (isinf (gap[LEFT]) || isinf(gap[RIGHT]))
683 continue;
685 if (gap.length () >= max_gap_len)
687 max_gap_len = gap.length();
688 max_gap = gap;
692 if (max_gap_len > threshold)
694 int j = 0;
695 for (int i = 0; i < stems.size(); i++)
697 Grob* stem = stems[i];
698 if (Stem::invisible_b (stem))
699 continue;
701 Interval hps = hps_array[j++];
704 Direction d = (hps.center () < max_gap.center()) ?
705 UP : DOWN ;
707 stem->set_grob_property ("direction", scm_int2num (d));
709 hps.intersect (max_gap);
710 assert (hps.empty_b () || hps.length () < 1e-6 );
717 /* Set stem's shorten property if unset.
719 TODO:
720 take some y-position (chord/beam/nearest?) into account
721 scmify forced-fraction
723 This is done in beam because the shorten has to be uniform over the
724 entire beam.
727 void
728 Beam::set_stem_shorten (Grob *me)
731 shortening looks silly for x staff beams
733 if (knee_b(me))
734 return ;
736 Real forced_fraction = 1.0 * forced_stem_count (me)
737 / visible_stem_count (me);
739 int beam_count = get_beam_count (me);
741 SCM shorten_list = me->get_grob_property ("beamed-stem-shorten");
742 if (shorten_list == SCM_EOL)
743 return;
745 Real staff_space = Staff_symbol_referencer::staff_space (me);
747 SCM shorten_elt =
748 robust_list_ref (beam_count -1, shorten_list);
749 Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
751 /* your similar cute comment here */
752 shorten_f *= forced_fraction;
754 if (shorten_f)
755 me->set_grob_property ("shorten", gh_double2scm (shorten_f));
758 /* Call list of y-dy-callbacks, that handle setting of
759 grob-properties
762 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
764 Beam::after_line_breaking (SCM smob)
766 Grob *me = unsmob_grob (smob);
768 /* Copy to mutable list. */
769 SCM s = ly_deep_copy (me->get_grob_property ("positions"));
770 me->set_grob_property ("positions", s);
772 if (ly_car (s) == SCM_BOOL_F)
775 // one wonders if such genericity is necessary --hwn.
776 SCM callbacks = me->get_grob_property ("position-callbacks");
777 for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
778 gh_call1 (ly_car (i), smob);
781 set_stem_lengths (me);
782 return SCM_UNSPECIFIED;
787 Compute a first approximation to the beam slope.
789 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
791 Beam::least_squares (SCM smob)
793 Grob *me = unsmob_grob (smob);
795 int count = visible_stem_count (me);
796 Interval pos (0, 0);
798 if (count <= 1)
800 me->set_grob_property ("positions", ly_interval2scm (pos));
801 return SCM_UNSPECIFIED;
805 Array<Real> x_posns ;
806 Link_array<Grob> stems=
807 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
808 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
809 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
811 Real my_y = me->relative_coordinate (commony, Y_AXIS);
813 Grob *fvs = first_visible_stem (me);
814 Grob *lvs = last_visible_stem (me);
816 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
817 + fvs->relative_coordinate (commony, Y_AXIS) -my_y,
818 Stem::get_stem_info (lvs).ideal_y_
819 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
821 Real x0 = first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
822 for (int i=0; i < stems.size (); i++)
824 Grob* s = stems[i];
826 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
827 x_posns.push (x);
829 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
831 Real y =0;
832 Real dydx = 0;
833 Real dy = 0;
835 if (!ideal.delta ())
837 Interval chord (Stem::chord_start_y (first_visible_stem (me)),
838 Stem::chord_start_y (last_visible_stem (me)));
840 /* Simple beams (2 stems) on middle line should be allowed to be
841 slightly sloped.
843 However, if both stems reach middle line,
844 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
846 For that case, we apply artificial slope */
847 if (!ideal[LEFT] && chord.delta () && count == 2)
849 /* FIXME. -> UP */
850 Direction d = (Direction) (sign (chord.delta ()) * UP);
851 pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
852 pos[-d] = - pos[d];
854 else
856 pos = ideal;
859 y = pos[LEFT];
860 dy = pos[RIGHT]- y;
861 dydx = dy/dx;
863 else
865 Array<Offset> ideals;
866 for (int i=0; i < stems.size (); i++)
868 Grob* s = stems[i];
869 if (Stem::invisible_b (s))
870 continue;
871 ideals.push (Offset (x_posns[i],
872 Stem::get_stem_info (s).ideal_y_
873 + s->relative_coordinate (commony, Y_AXIS)
874 - my_y));
876 minimise_least_squares (&dydx, &y, ideals);
878 dy = dydx * dx;
879 me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
880 pos = Interval (y, (y+dy));
883 me->set_grob_property ("positions", ly_interval2scm (pos));
885 return SCM_UNSPECIFIED;
890 We can't combine with previous function, since check concave and
891 slope damping comes first.
893 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
895 Beam::shift_region_to_valid (SCM grob)
897 Grob *me = unsmob_grob (grob);
899 Code dup.
901 Array<Real> x_posns ;
902 Link_array<Grob> stems=
903 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
904 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
905 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
907 Grob *fvs = first_visible_stem (me);
909 if (!fvs)
910 return SCM_UNSPECIFIED;
912 Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
913 for (int i=0; i < stems.size (); i++)
915 Grob* s = stems[i];
917 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
918 x_posns.push (x);
921 Grob *lvs = last_visible_stem (me);
922 if (!lvs)
923 return SCM_UNSPECIFIED;
925 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
927 Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
928 Real dy = pos.delta();
929 Real y = pos[LEFT];
930 Real dydx =dy/dx;
934 Shift the positions so that we have a chance of finding good
935 quants (i.e. no short stem failures.)
937 Interval feasible_left_point;
938 feasible_left_point.set_full ();
939 for (int i=0; i < stems.size (); i++)
941 Grob* s = stems[i];
942 if (Stem::invisible_b (s))
943 continue;
945 Direction d = Stem::get_direction (s);
947 Real left_y =
948 Stem::get_stem_info (s).shortest_y_
949 - dydx * x_posns [i];
952 left_y is now relative to the stem S. We want relative to
953 ourselves, so translate:
955 left_y +=
956 + s->relative_coordinate (commony, Y_AXIS)
957 - me->relative_coordinate (commony, Y_AXIS);
959 Interval flp ;
960 flp.set_full ();
961 flp[-d] = left_y;
963 feasible_left_point.intersect (flp);
966 if (feasible_left_point.empty_b())
968 warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
970 else if (!feasible_left_point.elem_b(y))
972 if (isinf (feasible_left_point[DOWN]))
973 y = feasible_left_point[UP] - REGION_SIZE;
974 else if (isinf (feasible_left_point[UP]))
975 y = feasible_left_point[DOWN]+ REGION_SIZE;
976 else
977 y = feasible_left_point.center ();
979 pos = Interval (y, (y+dy));
980 me->set_grob_property ("positions", ly_interval2scm (pos));
981 return SCM_UNSPECIFIED;
985 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
987 Beam::check_concave (SCM smob)
989 Grob *me = unsmob_grob (smob);
991 Link_array<Grob> stems =
992 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
994 for (int i = 0; i < stems.size ();)
996 if (Stem::invisible_b (stems[i]))
997 stems.del (i);
998 else
999 i++;
1002 if (stems.size () < 3)
1003 return SCM_UNSPECIFIED;
1006 /* Concaveness #1: If distance of an inner notehead to line between
1007 two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1008 beam is concave (Heinz Stolba).
1010 In the case of knees, the line connecting outer heads is often
1011 not related to the beam slope (it may even go in the other
1012 direction). Skip the check when the outer stems point in
1013 different directions. --hwn
1016 bool concaveness1 = false;
1017 SCM gap = me->get_grob_property ("concaveness-gap");
1018 if (gh_number_p (gap)
1019 && Stem::get_direction(stems.top ())
1020 == Stem::get_direction(stems[0]))
1022 Real r1 = gh_scm2double (gap);
1023 Real dy = Stem::chord_start_y (stems.top ())
1024 - Stem::chord_start_y (stems[0]);
1027 Real slope = dy / (stems.size () - 1);
1029 Real y0 = Stem::chord_start_y (stems[0]);
1030 for (int i = 1; i < stems.size () - 1; i++)
1032 Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1033 if (c > r1)
1035 concaveness1 = true;
1036 break;
1042 /* Concaveness #2: Sum distances of inner noteheads that fall
1043 outside the interval of the two outer noteheads.
1045 We only do this for beams where first and last stem have the same
1046 direction. --hwn.
1049 Note that "convex" stems compensate for "concave" stems.
1050 (is that intentional?) --hwn.
1053 Real concaveness2 = 0;
1054 SCM thresh = me->get_grob_property ("concaveness-threshold");
1055 Real r2 = infinity_f;
1056 if (!concaveness1 && gh_number_p (thresh)
1057 && Stem::get_direction(stems.top ())
1058 == Stem::get_direction(stems[0]))
1060 r2 = gh_scm2double (thresh);
1062 Direction dir = Stem::get_direction(stems.top ());
1063 Real concave = 0;
1064 Interval iv (Stem::chord_start_y (stems[0]),
1065 Stem::chord_start_y (stems.top ()));
1067 if (iv[MAX] < iv[MIN])
1068 iv.swap ();
1070 for (int i = 1; i < stems.size () - 1; i++)
1072 Real f = Stem::chord_start_y (stems[i]);
1073 concave += ((f - iv[MAX] ) >? 0) +
1074 ((f - iv[MIN] ) <? 0);
1076 concave *= dir;
1077 concaveness2 = concave / (stems.size () - 2);
1081 ugh: this is the a kludge to get
1082 input/regression/beam-concave.ly to behave as
1083 baerenreiter.
1088 huh? we're dividing twice (which is not scalable) meaning that
1089 the longer the beam, the more unlikely it will be
1090 concave. Maybe you would even expect the other way around??
1092 --hwn.
1095 concaveness2 /= (stems.size () - 2);
1098 /* TODO: some sort of damping iso -> plain horizontal */
1099 if (concaveness1 || concaveness2 > r2)
1101 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1102 Real r = pos.linear_combination (0);
1103 me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1104 me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1107 return SCM_UNSPECIFIED;
1110 /* This neat trick is by Werner Lemberg,
1111 damped = tanh (slope)
1112 corresponds with some tables in [Wanske] CHECKME */
1113 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1115 Beam::slope_damping (SCM smob)
1117 Grob *me = unsmob_grob (smob);
1119 if (visible_stem_count (me) <= 1)
1120 return SCM_UNSPECIFIED;
1122 SCM s = me->get_grob_property ("damping");
1123 int damping = gh_scm2int (s);
1125 if (damping)
1127 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1128 Real dy = pos.delta ();
1130 Grob *fvs = first_visible_stem (me);
1131 Grob *lvs = last_visible_stem (me);
1133 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1136 Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1137 - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1138 Real dydx = dy && dx ? dy/dx : 0;
1139 dydx = 0.6 * tanh (dydx) / damping;
1141 Real damped_dy = dydx * dx;
1142 pos[LEFT] += (dy - damped_dy) / 2;
1143 pos[RIGHT] -= (dy - damped_dy) / 2;
1145 me->set_grob_property ("positions", ly_interval2scm (pos));
1147 return SCM_UNSPECIFIED;
1151 Report slice containing the numbers that are both in (car BEAMING)
1152 and (cdr BEAMING)
1154 Slice
1155 where_are_the_whole_beams(SCM beaming)
1157 Slice l;
1159 for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1161 if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1163 l.add_point (gh_scm2int (gh_car (s)));
1166 return l;
1169 /* Return the Y position of the stem-end, given the Y-left, Y-right
1170 in POS for stem S. This Y position is relative to S. */
1171 Real
1172 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1173 Real xl, Real xr,
1174 Interval pos, bool french)
1176 Real beam_translation = get_beam_translation (me);
1179 Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1180 Real dy = pos.delta ();
1181 Real dx = xr - xl;
1182 Real stem_y_beam0 = (dy && dx
1183 ? r / dx
1184 * dy
1185 : 0) + pos[LEFT];
1187 Direction my_dir = Directional_element_interface::get (s);
1188 SCM beaming = s->get_grob_property ("beaming");
1190 Real stem_y = stem_y_beam0;
1191 if (french)
1193 Slice bm = where_are_the_whole_beams (beaming);
1194 if (!bm.empty_b())
1195 stem_y += beam_translation * bm[-my_dir];
1197 else
1199 Slice bm = Stem::beam_multiplicity(s);
1200 if (!bm.empty_b())
1201 stem_y +=bm[my_dir] * beam_translation;
1204 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1205 - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1207 return stem_y + id;
1211 Hmm. At this time, beam position and slope are determined. Maybe,
1212 stem directions and length should set to relative to the chord's
1213 position of the beam. */
1214 void
1215 Beam::set_stem_lengths (Grob *me)
1217 Link_array<Grob> stems=
1218 Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1220 if (stems.size () <= 1)
1221 return;
1223 Grob *common[2];
1224 for (int a = 2; a--;)
1225 common[a] = common_refpoint_of_array (stems, me, Axis(a));
1227 Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1228 Real staff_space = Staff_symbol_referencer::staff_space (me);
1230 bool french = to_boolean (me->get_grob_property ("french-beaming"));
1233 bool gap = false;
1234 Real thick =0.0;
1235 if (gh_number_p (me->get_grob_property ("gap"))
1236 &&gh_scm2double (me->get_grob_property ("gap")))
1238 gap = true;
1239 thick = get_thickness(me);
1242 // ugh -> use commonx
1243 Grob * fvs = first_visible_stem (me);
1244 Grob *lvs = last_visible_stem (me);
1246 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1247 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1249 for (int i=0; i < stems.size (); i++)
1251 Grob* s = stems[i];
1252 if (Stem::invisible_b (s))
1253 continue;
1255 Real stem_y = calc_stem_y (me, s, common,
1256 xl, xr,
1257 pos, french && i > 0&& (i < stems.size () -1));
1260 Make the stems go up to the end of the beam. This doesn't matter
1261 for normal beams, but for tremolo beams it looks silly otherwise.
1263 if (gap)
1264 stem_y += thick * 0.5 * Directional_element_interface::get(s);
1266 Stem::set_stemend (s, 2* stem_y / staff_space);
1270 void
1271 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1273 Link_array<Grob> stems=
1274 Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1276 Direction d = LEFT;
1277 for (int i=0; i < stems.size (); i++)
1280 Don't overwrite user settings.
1285 /* Don't set beaming for outside of outer stems */
1286 if ((d == LEFT && i == 0)
1287 ||(d == RIGHT && i == stems.size () -1))
1288 continue;
1290 Grob *st = stems[i];
1291 SCM beaming_prop = st->get_grob_property ("beaming");
1292 if (beaming_prop == SCM_EOL ||
1293 index_get_cell (beaming_prop, d) == SCM_EOL)
1295 int b = beaming->infos_.elem (i).beams_i_drul_[d];
1296 if (i>0
1297 && i < stems.size() -1
1298 && Stem::invisible_b (st))
1299 b = b <? beaming->infos_.elem(i).beams_i_drul_[-d];
1301 Stem::set_beaming (st, b, d);
1304 while (flip (&d) != LEFT);
1309 Beam::forced_stem_count (Grob *me)
1311 Link_array<Grob>stems =
1312 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1313 int f = 0;
1314 for (int i=0; i < stems.size (); i++)
1316 Grob *s = stems[i];
1318 if (Stem::invisible_b (s))
1319 continue;
1321 /* I can imagine counting those boundaries as a half forced stem,
1322 but let's count them full for now. */
1323 if (abs (Stem::chord_start_y (s)) > 0.1
1324 && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1325 f++;
1327 return f;
1334 Beam::visible_stem_count (Grob *me)
1336 Link_array<Grob>stems =
1337 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1338 int c = 0;
1339 for (int i = stems.size (); i--;)
1341 if (!Stem::invisible_b (stems[i]))
1342 c++;
1344 return c;
1347 Grob*
1348 Beam::first_visible_stem (Grob *me)
1350 Link_array<Grob>stems =
1351 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1353 for (int i = 0; i < stems.size (); i++)
1355 if (!Stem::invisible_b (stems[i]))
1356 return stems[i];
1358 return 0;
1361 Grob*
1362 Beam::last_visible_stem (Grob *me)
1364 Link_array<Grob>stems =
1365 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1366 for (int i = stems.size (); i--;)
1368 if (!Stem::invisible_b (stems[i]))
1369 return stems[i];
1371 return 0;
1376 [TODO]
1378 handle rest under beam (do_post: beams are calculated now)
1379 what about combination of collisions and rest under beam.
1381 Should lookup
1383 rest -> stem -> beam -> interpolate_y_position ()
1385 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1387 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1389 Grob *rest = unsmob_grob (element_smob);
1390 Axis a = (Axis) gh_scm2int (axis);
1392 assert (a == Y_AXIS);
1394 Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1395 Grob *stem = st;
1396 if (!stem)
1397 return gh_double2scm (0.0);
1398 Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1399 if (!beam
1400 || !Beam::has_interface (beam)
1401 || !Beam::visible_stem_count (beam))
1402 return gh_double2scm (0.0);
1404 Interval pos (0, 0);
1405 SCM s = beam->get_grob_property ("positions");
1406 if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1407 pos = ly_scm2interval (s);
1409 Real dy = pos.delta ();
1410 // ugh -> use commonx
1411 Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1412 Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1413 Real dydx = dy && dx ? dy/dx : 0;
1415 Direction d = Stem::get_direction (stem);
1416 Real stem_y = (pos[LEFT]
1417 + (stem->relative_coordinate (0, X_AXIS) - x0) * dydx)
1418 * d;
1420 Real beam_translation = get_beam_translation (beam);
1421 Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
1422 int beam_count = get_direction_beam_count (beam, d);
1423 Real height_of_my_beams = beam_thickness
1424 + (beam_count - 1) * beam_translation;
1425 Real beam_y = stem_y - height_of_my_beams + beam_thickness / 2.0;
1427 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1429 /* Better calculate relative-distance directly, rather than using
1430 rest_dim? */
1431 Grob *common_x = rest->common_refpoint (beam, Y_AXIS);
1432 Real rest_dim = rest->extent (common_x, Y_AXIS)[d] / staff_space * d;
1434 Real minimum_distance = gh_scm2double
1435 (rest->get_grob_property ("minimum-beam-collision-distance"));
1437 Real distance = beam_y - rest_dim;
1438 Real shift = 0;
1439 if (distance < 0)
1440 shift = minimum_distance - distance;
1441 else if (minimum_distance > distance)
1442 shift = minimum_distance - distance;
1444 int stafflines = Staff_symbol_referencer::line_count (rest);
1446 /* Always move discretely by half spaces */
1447 Real discrete_shift = ceil (shift * 2.0) / 2.0;
1449 /* Inside staff, move by whole spaces*/
1450 if ((rest->extent (common_x, Y_AXIS)[d] + discrete_shift) * d
1451 < stafflines / 2.0
1452 ||(rest->extent (common_x, Y_AXIS)[-d] + discrete_shift) * -d
1453 < stafflines / 2.0)
1454 discrete_shift = ceil (discrete_shift);
1456 return gh_double2scm (-d * discrete_shift);
1459 bool
1460 Beam::knee_b (Grob* me)
1462 SCM k = me->get_grob_property ("knee");
1463 if (gh_boolean_p (k))
1464 return gh_scm2bool (k);
1466 bool knee = false;
1467 int d = 0;
1468 for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1470 Direction dir = Directional_element_interface::get
1471 (unsmob_grob (ly_car (s)));
1472 if (d && d != dir)
1474 knee = true;
1475 break;
1477 d = dir;
1480 me->set_grob_property ("knee", gh_bool2scm (knee));
1482 return knee;
1486 Beam::get_direction_beam_count (Grob *me, Direction d )
1488 Link_array<Grob>stems =
1489 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1490 int bc = 0;
1492 for (int i = stems.size (); i--;)
1495 Should we take invisible stems into account?
1497 if (Stem::get_direction (stems[i]) == d)
1498 bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1501 return bc;
1505 ADD_INTERFACE (Beam, "beam-interface",
1506 "A beam. \n\n"
1508 "#'thickness= weight of beams, in staffspace "
1511 "We take the least squares line through the ideal-length stems, and "
1512 "then damp that using "
1513 " \n"
1514 " damped = tanh (slope) \n"
1515 " \n"
1516 "this gives an unquantized left and right position for the beam end. "
1517 "Then we take all combinations of quantings near these left and right "
1518 "positions, and give them a score (according to how close they are to "
1519 "the ideal slope, how close the result is to the ideal stems, etc.). We "
1520 "take the best scoring combination. "
1522 "knee 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");