Correct left text alignment of DynamicTextSpanner.
[lilypond.git] / lily / beam.cc
blob5cc6c7c84587e436cdfae706bf7ca92c103ac722
1 /*
2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
8 */
11 TODO:
13 - Determine auto knees based on positions if it's set by the user.
15 - the code is littered with * and / staff_space calls for
16 #'positions. Consider moving to real-world coordinates?
18 Problematic issue is user tweaks (user tweaks are in staff-coordinates.)
20 Notes:
22 - Stems run to the Y-center of the beam.
24 - beam_translation is the offset between Y centers of the beam.
27 #include "beam.hh"
29 #include "beaming-pattern.hh"
30 #include "directional-element-interface.hh"
31 #include "main.hh"
32 #include "international.hh"
33 #include "interval-set.hh"
34 #include "item.hh"
35 #include "least-squares.hh"
36 #include "lookup.hh"
37 #include "misc.hh"
38 #include "output-def.hh"
39 #include "pointer-group-interface.hh"
40 #include "spanner.hh"
41 #include "staff-symbol-referencer.hh"
42 #include "stem.hh"
43 #include "warn.hh"
44 #include "grob-array.hh"
46 #if DEBUG_BEAM_SCORING
47 #include "text-interface.hh" // debug output.
48 #include "font-interface.hh" // debug output.
49 #endif
51 #include <map>
54 Beam_stem_segment::Beam_stem_segment ()
56 max_connect_ = 1000; // infinity
57 stem_ = 0;
58 width_ = 0.0;
59 stem_x_ = 0.0;
60 rank_ = 0;
61 stem_index_ = 0;
62 dir_ = CENTER;
65 Beam_segment::Beam_segment ()
67 vertical_count_ = 0;
70 void
71 Beam::add_stem (Grob *me, Grob *s)
73 if (Stem::get_beam (s))
75 programming_error ("Stem already has beam");
76 return ;
79 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
80 s->set_object ("beam", me->self_scm ());
81 add_bound_item (dynamic_cast<Spanner *> (me), dynamic_cast<Item *> (s));
84 Real
85 Beam::get_thickness (Grob *me)
87 return robust_scm2double (me->get_property ("thickness"), 0)
88 * Staff_symbol_referencer::staff_space (me);
91 /* Return the translation between 2 adjoining beams. */
92 Real
93 Beam::get_beam_translation (Grob *me)
95 int beam_count = get_beam_count (me);
96 Real staff_space = Staff_symbol_referencer::staff_space (me);
97 Real line = Staff_symbol_referencer::line_thickness (me);
98 Real thickness = get_thickness (me);
99 Real fract = robust_scm2double (me->get_property ("length-fraction"), 1.0);
101 Real beam_translation = beam_count < 4
102 ? (2 * staff_space + line - thickness) / 2.0
103 : (3 * staff_space + line - thickness) / 3.0;
105 return fract * beam_translation;
108 /* Maximum beam_count. */
110 Beam::get_beam_count (Grob *me)
112 int m = 0;
114 extract_grob_set (me, "stems", stems);
115 for (vsize i = 0; i < stems.size (); i++)
117 Grob *stem = stems[i];
118 m = max (m, (Stem::beam_multiplicity (stem).length () + 1));
120 return m;
123 MAKE_SCHEME_CALLBACK (Beam, calc_normal_stems, 1);
125 Beam::calc_normal_stems (SCM smob)
127 Grob *me = unsmob_grob (smob);
129 extract_grob_set (me, "stems", stems);
130 SCM val = Grob_array::make_array ();
131 Grob_array *ga = unsmob_grob_array (val);
132 for (vsize i = 0; i < stems.size (); i++)
133 if (Stem::is_normal_stem (stems[i]))
134 ga->add (stems[i]);
136 return val;
139 MAKE_SCHEME_CALLBACK (Beam, calc_direction, 1);
141 Beam::calc_direction (SCM smob)
143 Grob *me = unsmob_grob (smob);
145 /* Beams with less than 2 two stems don't make much sense, but could happen
146 when you do
148 r8[ c8 r8]
152 Direction dir = CENTER;
154 int count = normal_stem_count (me);
155 if (count < 2)
157 extract_grob_set (me, "stems", stems);
158 if (stems.size () == 0)
160 me->warning (_ ("removing beam with no stems"));
161 me->suicide ();
163 return SCM_UNSPECIFIED;
165 else
167 Grob *stem = first_normal_stem (me);
170 This happens for chord tremolos.
172 if (!stem)
173 stem = stems[0];
175 if (is_direction (stem->get_property_data ("direction")))
176 dir = to_dir (stem->get_property_data ("direction"));
177 else
178 dir = to_dir (stem->get_property ("default-direction"));
182 if (count >= 1)
184 if (!dir)
185 dir = get_default_dir (me);
187 consider_auto_knees (me);
190 if (dir)
192 set_stem_directions (me, dir);
195 return scm_from_int (dir);
200 /* We want a maximal number of shared beams, but if there is choice, we
201 * take the one that is closest to the end of the stem. This is for
202 * situations like
207 * |===|
208 * |=
213 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
214 Direction left_dir,
215 Direction right_dir)
217 Slice lslice = int_list_to_slice (scm_cdr (left_beaming));
219 int best_count = 0;
220 int best_start = 0;
221 for (int i = lslice[-left_dir];
222 (i - lslice[left_dir]) * left_dir <= 0; i += left_dir)
224 int count = 0;
225 for (SCM s = scm_car (right_beaming); scm_is_pair (s); s = scm_cdr (s))
227 int k = -right_dir * scm_to_int (scm_car (s)) + i;
228 if (scm_c_memq (scm_from_int (k), left_beaming) != SCM_BOOL_F)
229 count++;
232 if (count >= best_count)
234 best_count = count;
235 best_start = i;
239 return best_start;
242 MAKE_SCHEME_CALLBACK (Beam, calc_beaming, 1)
244 Beam::calc_beaming (SCM smob)
246 Grob *me = unsmob_grob (smob);
248 extract_grob_set (me, "stems", stems);
250 Slice last_int;
251 last_int.set_empty ();
253 SCM last_beaming = scm_cons (SCM_EOL, scm_list_1 (scm_from_int (0)));
254 Direction last_dir = CENTER;
255 for (vsize i = 0; i < stems.size (); i++)
257 Grob *this_stem = stems[i];
258 SCM this_beaming = this_stem->get_property ("beaming");
260 Direction this_dir = get_grob_direction (this_stem);
261 if (scm_is_pair (last_beaming) && scm_is_pair (this_beaming))
263 int start_point = position_with_maximal_common_beams
264 (last_beaming, this_beaming,
265 last_dir ? last_dir : this_dir,
266 this_dir);
268 Direction d = LEFT;
269 Slice new_slice;
272 new_slice.set_empty ();
273 SCM s = index_get_cell (this_beaming, d);
274 for (; scm_is_pair (s); s = scm_cdr (s))
276 int new_beam_pos
277 = start_point - this_dir * scm_to_int (scm_car (s));
279 new_slice.add_point (new_beam_pos);
280 scm_set_car_x (s, scm_from_int (new_beam_pos));
283 while (flip (&d) != LEFT);
285 if (!new_slice.is_empty ())
286 last_int = new_slice;
288 else
291 FIXME: what's this for?
293 SCM s = scm_cdr (this_beaming);
294 for (; scm_is_pair (s); s = scm_cdr (s))
296 int np = -this_dir * scm_to_int (scm_car (s));
297 scm_set_car_x (s, scm_from_int (np));
298 last_int.add_point (np);
302 if (scm_ilength (scm_cdr (this_beaming)) > 0)
304 last_beaming = this_beaming;
305 last_dir = this_dir;
309 return SCM_EOL;
312 bool
313 operator <(Beam_stem_segment const &a,
314 Beam_stem_segment const &b)
316 return a.rank_ < b.rank_;
319 typedef map<int, vector<Beam_stem_segment> > Position_stem_segments_map;
321 vector<Beam_segment>
322 Beam::get_beam_segments (Grob *me_grob, Grob **common)
324 /* ugh, this has a side-effect that we need to ensure that
325 Stem #'beaming is correct */
326 (void) me_grob->get_property ("beaming");
328 Spanner *me = dynamic_cast<Spanner*> (me_grob);
330 extract_grob_set (me, "stems", stems);
331 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
333 commonx = me->get_bound (LEFT)->common_refpoint (commonx, X_AXIS);
334 commonx = me->get_bound (RIGHT)->common_refpoint (commonx, X_AXIS);
336 *common = commonx;
338 int gap_count = robust_scm2int (me->get_property ("gap-count"), 0);
339 Real gap_length = robust_scm2double (me->get_property ("gap"), 0.0);
341 Position_stem_segments_map stem_segments;
342 Real lt = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
344 Slice ranks;
345 for (vsize i = 0; i < stems.size (); i++)
347 Grob *stem = stems[i];
348 Real stem_width = robust_scm2double (stem->get_property ("thickness"), 1.0) * lt;
349 Real stem_x = stem->relative_coordinate (commonx, X_AXIS);
350 SCM beaming = stem->get_property ("beaming");
351 Direction d = LEFT;
354 for (SCM s = index_get_cell (beaming, d);
355 scm_is_pair (s); s = scm_cdr (s))
357 if (!scm_is_integer (scm_car (s)))
358 continue;
360 int beam_rank = scm_to_int (scm_car (s));
361 ranks.add_point (beam_rank);
364 for (SCM s = index_get_cell (beaming, d);
365 scm_is_pair (s); s = scm_cdr (s))
367 if (!scm_is_integer (scm_car (s)))
368 continue;
370 int beam_rank = scm_to_int (scm_car (s));
371 Beam_stem_segment seg;
372 seg.stem_ = stem;
373 seg.stem_x_ = stem_x;
374 seg.rank_ = 2 * i + (d+1)/2;
375 seg.width_ = stem_width;
376 seg.stem_index_ = i;
377 seg.dir_ = d;
378 seg.max_connect_ = robust_scm2int (stem->get_property ("max-beam-connect"), 1000);
380 Direction stem_dir = get_grob_direction (stem);
382 seg.gapped_
383 = (stem_dir * beam_rank < (stem_dir * ranks[-stem_dir] + gap_count));
384 stem_segments[beam_rank].push_back (seg);
387 while (flip (&d) != LEFT);
390 Drul_array<Real> break_overshoot
391 = robust_scm2drul (me->get_property ("break-overshoot"),
392 Drul_array<Real> (-0.5, 0.0));
394 vector<Beam_segment> segments;
395 for (Position_stem_segments_map::const_iterator i (stem_segments.begin ());
396 i != stem_segments.end (); i++)
398 vector<Beam_stem_segment> segs = (*i).second;
399 vector_sort (segs, less<Beam_stem_segment> ());
401 Beam_segment current;
403 int vertical_count = (*i).first;
404 for (vsize j = 0; j < segs.size (); j++)
407 event_dir == LEFT: left edge of a beamsegment.
409 Direction event_dir = LEFT;
412 bool on_line_bound = (segs[j].dir_ == LEFT) ? segs[j].stem_index_ == 0
413 : segs[j].stem_index_ == stems.size() - 1;
414 bool on_beam_bound = (event_dir == LEFT) ? j == 0 :
415 j == segs.size () - 1;
416 bool inside_stem = (event_dir == LEFT)
417 ? segs[j].stem_index_ > 0
418 : segs[j].stem_index_ + 1 < stems.size () ;
420 bool event = on_beam_bound
421 || abs (segs[j].rank_ - segs[j+event_dir].rank_) > 1
422 || (abs (vertical_count) >= segs[j].max_connect_
423 || abs (vertical_count) >= segs[j + event_dir].max_connect_);
425 if (!event)
426 continue;
428 current.vertical_count_ = vertical_count;
429 current.horizontal_[event_dir] = segs[j].stem_x_;
430 if (segs[j].dir_ == event_dir)
432 if (on_line_bound
433 && me->get_bound (event_dir)->break_status_dir ())
435 current.horizontal_[event_dir]
436 = (robust_relative_extent (me->get_bound (event_dir),
437 commonx, X_AXIS)[RIGHT]
438 + event_dir * break_overshoot[event_dir]);
440 else
442 Real notehead_width =
443 Stem::duration_log (segs[j].stem_) == 1
444 ? 1.98
445 : 1.32; // URG.
448 if (inside_stem)
450 Grob *neighbor_stem = stems[segs[j].stem_index_ + event_dir];
451 Real neighbor_stem_x
452 = neighbor_stem->relative_coordinate (commonx, X_AXIS);
454 notehead_width = min (notehead_width,
455 fabs (neighbor_stem_x - segs[j].stem_x_)/2.5);
457 current.horizontal_[event_dir] += event_dir * notehead_width;
460 else
462 current.horizontal_[event_dir] += event_dir * segs[j].width_/2;
463 if (segs[j].gapped_)
465 current.horizontal_[event_dir] -= event_dir * gap_length;
467 if (Stem::is_invisible (segs[j].stem_))
470 Need to do this in case of whole notes. We don't want the
471 heads to collide with the beams.
473 extract_grob_set (segs[j].stem_, "note-heads", heads);
475 for (vsize k = 0; k < heads.size (); k ++)
476 current.horizontal_[event_dir]
477 = event_dir * min (event_dir * current.horizontal_[event_dir],
478 - gap_length/2
479 + event_dir
480 * heads[k]->extent (commonx,
481 X_AXIS)[-event_dir]);
486 if (event_dir == RIGHT)
488 segments.push_back (current);
489 current = Beam_segment ();
492 while (flip (&event_dir) != LEFT);
497 return segments;
500 MAKE_SCHEME_CALLBACK (Beam, print, 1);
502 Beam::print (SCM grob)
504 Spanner *me = unsmob_spanner (grob);
505 Grob *commonx = 0;
506 vector<Beam_segment> segments = get_beam_segments (me, &commonx);
508 Interval span;
509 if (normal_stem_count (me))
511 span[LEFT] = first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
512 span[RIGHT] = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
514 else
516 extract_grob_set (me, "stems", stems);
517 span[LEFT] = stems[0]->relative_coordinate (commonx, X_AXIS);
518 span[RIGHT] = stems.back ()->relative_coordinate (commonx, X_AXIS);
521 Real blot = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
523 SCM posns = me->get_property ("quantized-positions");
524 Interval pos;
525 if (!is_number_pair (posns))
527 programming_error ("no beam positions?");
528 pos = Interval (0, 0);
530 else
531 pos = ly_scm2realdrul (posns);
533 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
535 Real dy = pos[RIGHT] - pos[LEFT];
536 Real slope = (dy && span.length ()) ? dy / span.length () : 0;
538 Real thick = get_thickness (me);
539 Real beam_dy = get_beam_translation (me);
541 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
543 Stencil the_beam;
544 for (vsize i = 0; i < segments.size (); i ++)
546 Real local_slope = slope;
547 if (feather_dir)
549 local_slope += feather_dir * segments[i].vertical_count_ * beam_dy / span.length ();
552 Stencil b = Lookup::beam (local_slope, segments[i].horizontal_.length (), thick, blot);
554 b.translate_axis (segments[i].horizontal_[LEFT], X_AXIS);
556 b.translate_axis (local_slope
557 * (segments[i].horizontal_[LEFT] - span.linear_combination (feather_dir))
558 + pos.linear_combination (feather_dir)
559 + beam_dy * segments[i].vertical_count_, Y_AXIS);
560 the_beam.add_stencil (b);
563 #if (DEBUG_BEAM_SCORING)
564 SCM annotation = me->get_property ("annotation");
565 if (!scm_is_string (annotation))
567 SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-beam-scoring"));
568 if (to_boolean (debug))
569 annotation = me->get_property ("quant-score");
572 if (scm_is_string (annotation))
574 extract_grob_set (me, "stems", stems);
577 This code prints the demerits for each beam. Perhaps this
578 should be switchable for those who want to twiddle with the
579 parameters.
581 string str;
582 SCM properties = Font_interface::text_font_alist_chain (me);
584 Direction stem_dir = stems.size () ? to_dir (stems[0]->get_property ("direction")) : UP;
586 Stencil score = *unsmob_stencil (Text_interface::interpret_markup
587 (me->layout ()->self_scm (), properties, annotation));
589 if (!score.is_empty ())
591 score.translate_axis (me->relative_coordinate(commonx, X_AXIS), X_AXIS);
592 the_beam.add_at_edge (Y_AXIS, stem_dir, score, 1.0);
595 #endif
597 the_beam.translate_axis (-me->relative_coordinate (commonx, X_AXIS), X_AXIS);
598 return the_beam.smobbed_copy ();
601 Direction
602 Beam::get_default_dir (Grob *me)
604 extract_grob_set (me, "stems", stems);
606 Drul_array<Real> extremes (0.0, 0.0);
607 for (iterof (s, stems); s != stems.end (); s++)
609 Interval positions = Stem::head_positions (*s);
610 Direction d = DOWN;
613 if (sign (positions[d]) == d)
614 extremes[d] = d * max (d * positions[d], d * extremes[d]);
616 while (flip (&d) != DOWN);
619 Drul_array<int> total (0, 0);
620 Drul_array<int> count (0, 0);
622 bool force_dir = false;
623 for (vsize i = 0; i < stems.size (); i++)
625 Grob *s = stems[i];
626 Direction stem_dir = CENTER;
627 SCM stem_dir_scm = s->get_property_data ("direction");
628 if (is_direction (stem_dir_scm))
630 stem_dir = to_dir (stem_dir_scm);
631 force_dir = true;
633 else
634 stem_dir = to_dir (s->get_property ("default-direction"));
636 if (!stem_dir)
637 stem_dir = to_dir (s->get_property ("neutral-direction"));
639 if (stem_dir)
641 count[stem_dir] ++;
642 total[stem_dir] += max (int (- stem_dir * Stem::head_positions (s) [-stem_dir]), 0);
647 if (!force_dir)
649 if (abs (extremes[UP]) > -extremes[DOWN])
650 return DOWN;
651 else if (extremes[UP] < -extremes[DOWN])
652 return UP;
655 Direction dir = CENTER;
656 Direction d = CENTER;
657 if ((d = (Direction) sign (count[UP] - count[DOWN])))
658 dir = d;
659 else if (count[UP]
660 && count[DOWN]
661 && (d = (Direction) sign (total[UP] / count[UP] - total[DOWN]/count[DOWN])))
662 dir = d;
663 else if ((d = (Direction) sign (total[UP] - total[DOWN])))
664 dir = d;
665 else
666 dir = to_dir (me->get_property ("neutral-direction"));
668 return dir;
671 /* Set all stems with non-forced direction to beam direction.
672 Urg: non-forced should become `without/with unforced' direction,
673 once stem gets cleaned-up. */
674 void
675 Beam::set_stem_directions (Grob *me, Direction d)
677 extract_grob_set (me, "stems", stems);
679 for (vsize i = 0; i < stems.size (); i++)
681 Grob *s = stems[i];
683 SCM forcedir = s->get_property_data ("direction");
684 if (!to_dir (forcedir))
685 set_grob_direction (s, d);
690 Only try horizontal beams for knees. No reliable detection of
691 anything else is possible here, since we don't know funky-beaming
692 settings, or X-distances (slopes!) People that want sloped
693 knee-beams, should set the directions manually.
696 TODO:
698 this routine should take into account the stemlength scoring
699 of a possible knee/nonknee beam.
701 void
702 Beam::consider_auto_knees (Grob *me)
704 SCM scm = me->get_property ("auto-knee-gap");
705 if (!scm_is_number (scm))
706 return;
708 Interval_set gaps;
710 gaps.set_full ();
712 extract_grob_set (me, "normal-stems", stems);
714 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
715 Real staff_space = Staff_symbol_referencer::staff_space (me);
717 vector<Interval> head_extents_array;
718 for (vsize i = 0; i < stems.size (); i++)
720 Grob *stem = stems[i];
722 Interval head_extents = Stem::head_positions (stem);
723 if (!head_extents.is_empty ())
725 head_extents[LEFT] += -1;
726 head_extents[RIGHT] += 1;
727 head_extents *= staff_space * 0.5;
730 We could subtract beam Y position, but this routine only
731 sets stem directions, a constant shift does not have an
732 influence.
734 head_extents += stem->pure_relative_y_coordinate (common, 0, INT_MAX);
736 if (to_dir (stem->get_property_data ("direction")))
738 Direction stemdir = to_dir (stem->get_property ("direction"));
739 head_extents[-stemdir] = -stemdir * infinity_f;
742 head_extents_array.push_back (head_extents);
744 gaps.remove_interval (head_extents);
747 Interval max_gap;
748 Real max_gap_len = 0.0;
750 for (vsize i = gaps.allowed_regions_.size () -1; i != VPOS ;i--)
752 Interval gap = gaps.allowed_regions_[i];
755 the outer gaps are not knees.
757 if (isinf (gap[LEFT]) || isinf (gap[RIGHT]))
758 continue;
760 if (gap.length () >= max_gap_len)
762 max_gap_len = gap.length ();
763 max_gap = gap;
767 Real beam_translation = get_beam_translation (me);
768 Real beam_thickness = Beam::get_thickness (me);
769 int beam_count = Beam::get_beam_count (me);
770 Real height_of_beams = beam_thickness / 2
771 + (beam_count - 1) * beam_translation;
772 Real threshold = scm_to_double (scm) + height_of_beams;
774 if (max_gap_len > threshold)
776 int j = 0;
777 for (vsize i = 0; i < stems.size (); i++)
779 Grob *stem = stems[i];
780 Interval head_extents = head_extents_array[j++];
782 Direction d = (head_extents.center () < max_gap.center ())
783 ? UP : DOWN;
785 stem->set_property ("direction", scm_from_int (d));
787 head_extents.intersect (max_gap);
788 assert (head_extents.is_empty () || head_extents.length () < 1e-6);
793 /* Set stem's shorten property if unset.
795 TODO:
796 take some y-position (chord/beam/nearest?) into account
797 scmify forced-fraction
799 This is done in beam because the shorten has to be uniform over the
800 entire beam.
805 void
806 set_minimum_dy (Grob *me, Real *dy)
808 if (*dy)
811 If dy is smaller than the smallest quant, we
812 get absurd direction-sign penalties.
815 Real ss = Staff_symbol_referencer::staff_space (me);
816 Real thickness = Beam::get_thickness (me) / ss;
817 Real slt = Staff_symbol_referencer::line_thickness (me) / ss;
818 Real sit = (thickness - slt) / 2;
819 Real inter = 0.5;
820 Real hang = 1.0 - (thickness - slt) / 2;
822 *dy = sign (*dy) * max (fabs (*dy),
823 min (min (sit, inter), hang));
829 MAKE_SCHEME_CALLBACK (Beam, calc_stem_shorten, 1)
831 Beam::calc_stem_shorten (SCM smob)
833 Grob *me = unsmob_grob (smob);
836 shortening looks silly for x staff beams
838 if (is_knee (me))
839 return scm_from_int (0);
841 Real forced_fraction = 1.0 * forced_stem_count (me)
842 / normal_stem_count (me);
844 int beam_count = get_beam_count (me);
846 SCM shorten_list = me->get_property ("beamed-stem-shorten");
847 if (shorten_list == SCM_EOL)
848 return scm_from_int (0);
850 Real staff_space = Staff_symbol_referencer::staff_space (me);
852 SCM shorten_elt
853 = robust_list_ref (beam_count -1, shorten_list);
854 Real shorten = scm_to_double (shorten_elt) * staff_space;
856 shorten *= forced_fraction;
859 if (shorten)
860 return scm_from_double (shorten);
862 return scm_from_double (0.0);
866 Interval
867 Beam::no_visible_stem_positions (Grob *me, Interval default_value)
869 extract_grob_set (me, "stems", stems);
870 if (stems.empty ())
871 return default_value;
873 Interval head_positions;
874 Slice multiplicity;
875 for (vsize i = 0; i < stems.size(); i++)
877 head_positions.unite (Stem::head_positions (stems[i]));
878 multiplicity.unite (Stem::beam_multiplicity (stems[i]));
881 Direction dir = get_grob_direction (me);
882 Real y = head_positions[dir]
883 * 0.5 * Staff_symbol_referencer::staff_space (me)
884 + dir * get_beam_translation (me) * (multiplicity.length () + 1);
886 y /= Staff_symbol_referencer::staff_space (me);
887 return Interval (y,y);
892 Compute a first approximation to the beam slope.
894 MAKE_SCHEME_CALLBACK (Beam, calc_least_squares_positions, 2);
896 Beam::calc_least_squares_positions (SCM smob, SCM posns)
898 (void) posns;
900 Grob *me = unsmob_grob (smob);
902 int count = normal_stem_count (me);
903 Interval pos (0,0);
904 if (count < 1)
905 return ly_interval2scm (no_visible_stem_positions (me, pos));
907 vector<Real> x_posns;
908 extract_grob_set (me, "normal-stems", stems);
909 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
910 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
912 Real my_y = me->relative_coordinate (commony, Y_AXIS);
914 Grob *fvs = first_normal_stem (me);
915 Grob *lvs = last_normal_stem (me);
917 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
918 + fvs->relative_coordinate (commony, Y_AXIS) - my_y,
919 Stem::get_stem_info (lvs).ideal_y_
920 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
922 Real x0 = first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
923 for (vsize i = 0; i < stems.size (); i++)
925 Grob *s = stems[i];
927 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
928 x_posns.push_back (x);
930 Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
932 Real y = 0;
933 Real slope = 0;
934 Real dy = 0;
935 Real ldy = 0.0;
936 if (!ideal.delta ())
938 Interval chord (Stem::chord_start_y (stems[0]),
939 Stem::chord_start_y (stems.back ()));
941 /* Simple beams (2 stems) on middle line should be allowed to be
942 slightly sloped.
944 However, if both stems reach middle line,
945 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
947 For that case, we apply artificial slope */
948 if (!ideal[LEFT] && chord.delta () && count == 2)
950 /* FIXME. -> UP */
951 Direction d = (Direction) (sign (chord.delta ()) * UP);
952 pos[d] = get_thickness (me) / 2;
953 pos[-d] = -pos[d];
955 else
956 pos = ideal;
959 For broken beams this doesn't work well. In this case, the
960 slope esp. of the first part of a broken beam should predict
961 where the second part goes.
963 ldy = pos[RIGHT] - pos[LEFT];
965 else
967 vector<Offset> ideals;
968 for (vsize i = 0; i < stems.size (); i++)
970 Grob *s = stems[i];
971 ideals.push_back (Offset (x_posns[i],
972 Stem::get_stem_info (s).ideal_y_
973 + s->relative_coordinate (commony, Y_AXIS)
974 - my_y));
977 minimise_least_squares (&slope, &y, ideals);
979 dy = slope * dx;
981 set_minimum_dy (me, &dy);
983 ldy = dy;
984 pos = Interval (y, (y + dy));
988 "position" is relative to the staff.
990 scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
992 me->set_property ("least-squares-dy", scm_from_double (ldy));
993 return ly_interval2scm (pos);
997 We can't combine with previous function, since check concave and
998 slope damping comes first.
1000 TODO: we should use the concaveness to control the amount of damping
1001 applied.
1003 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 2);
1005 Beam::shift_region_to_valid (SCM grob, SCM posns)
1007 Grob *me = unsmob_grob (grob);
1009 Code dup.
1011 vector<Real> x_posns;
1012 extract_grob_set (me, "stems", stems);
1013 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
1014 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
1016 Grob *fvs = first_normal_stem (me);
1018 if (!fvs)
1019 return posns;
1021 Real x0 = fvs->relative_coordinate (commonx, X_AXIS);
1022 for (vsize i = 0; i < stems.size (); i++)
1024 Grob *s = stems[i];
1026 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
1027 x_posns.push_back (x);
1030 Grob *lvs = last_normal_stem (me);
1031 if (!lvs)
1032 return posns;
1034 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
1036 Drul_array<Real> pos = ly_scm2interval (posns);
1038 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
1040 Real dy = pos[RIGHT] - pos[LEFT];
1041 Real y = pos[LEFT];
1042 Real slope = dx ? (dy / dx) : 0.0;
1045 Shift the positions so that we have a chance of finding good
1046 quants (i.e. no short stem failures.)
1048 Interval feasible_left_point;
1049 feasible_left_point.set_full ();
1050 for (vsize i = 0; i < stems.size (); i++)
1052 Grob *s = stems[i];
1053 if (Stem::is_invisible (s))
1054 continue;
1056 Direction d = get_grob_direction (s);
1058 Real left_y
1059 = Stem::get_stem_info (s).shortest_y_
1060 - slope * x_posns [i];
1063 left_y is now relative to the stem S. We want relative to
1064 ourselves, so translate:
1066 left_y
1067 += + s->relative_coordinate (commony, Y_AXIS)
1068 - me->relative_coordinate (commony, Y_AXIS);
1070 Interval flp;
1071 flp.set_full ();
1072 flp[-d] = left_y;
1074 feasible_left_point.intersect (flp);
1077 if (feasible_left_point.is_empty ())
1078 warning (_ ("no viable initial configuration found: may not find good beam slope"));
1079 else if (!feasible_left_point.contains (y))
1081 const int REGION_SIZE = 2; // UGH UGH
1082 if (isinf (feasible_left_point[DOWN]))
1083 y = feasible_left_point[UP] - REGION_SIZE;
1084 else if (isinf (feasible_left_point[UP]))
1085 y = feasible_left_point[DOWN]+ REGION_SIZE;
1086 else
1087 y = feasible_left_point.center ();
1090 pos = Drul_array<Real> (y, (y + dy));
1091 scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
1093 return ly_interval2scm (pos);
1096 /* This neat trick is by Werner Lemberg,
1097 damped = tanh (slope)
1098 corresponds with some tables in [Wanske] CHECKME */
1099 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 2);
1101 Beam::slope_damping (SCM smob, SCM posns)
1103 Grob *me = unsmob_grob (smob);
1104 Drul_array<Real> pos = ly_scm2interval (posns);
1106 if (normal_stem_count (me) <= 1)
1107 return posns;
1110 SCM s = me->get_property ("damping");
1111 Real damping = scm_to_double (s);
1112 Real concaveness = robust_scm2double (me->get_property ("concaveness"), 0.0);
1113 if (concaveness >= 10000)
1115 pos[LEFT] = pos[RIGHT];
1116 me->set_property ("least-squares-dy", scm_from_double (0));
1117 damping = 0;
1120 if (damping)
1122 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
1124 Real dy = pos[RIGHT] - pos[LEFT];
1126 Grob *fvs = first_normal_stem (me);
1127 Grob *lvs = last_normal_stem (me);
1129 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1131 Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS)
1132 - first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
1134 Real slope = dy && dx ? dy / dx : 0;
1136 slope = 0.6 * tanh (slope) / (damping + concaveness);
1138 Real damped_dy = slope * dx;
1140 set_minimum_dy (me, &damped_dy);
1142 pos[LEFT] += (dy - damped_dy) / 2;
1143 pos[RIGHT] -= (dy - damped_dy) / 2;
1145 scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
1148 return ly_interval2scm (pos);
1152 Report slice containing the numbers that are both in (car BEAMING)
1153 and (cdr BEAMING)
1155 Slice
1156 where_are_the_whole_beams (SCM beaming)
1158 Slice l;
1160 for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
1162 if (scm_c_memq (scm_car (s), scm_cdr (beaming)) != SCM_BOOL_F)
1164 l.add_point (scm_to_int (scm_car (s)));
1167 return l;
1170 /* Return the Y position of the stem-end, given the Y-left, Y-right
1171 in POS for stem S. This Y position is relative to S. */
1172 Real
1173 Beam::calc_stem_y (Grob *me, Grob *stem, Grob **common,
1174 Real xl, Real xr, Direction feather_dir,
1175 Drul_array<Real> pos, bool french)
1177 Real beam_translation = get_beam_translation (me);
1178 Direction stem_dir = get_grob_direction (stem);
1180 Real dx = xr - xl;
1181 Real relx = dx ? (stem->relative_coordinate (common[X_AXIS], X_AXIS) - xl)/dx : 0;
1182 Real xdir = 2*relx-1;
1184 Real stem_y = linear_combination(pos, xdir);
1186 SCM beaming = stem->get_property ("beaming");
1188 Slice beam_slice (french
1189 ? where_are_the_whole_beams (beaming)
1190 : Stem::beam_multiplicity (stem));
1191 if (beam_slice.is_empty ())
1192 beam_slice = Slice (0,0);
1193 Interval beam_multiplicity(beam_slice[LEFT],
1194 beam_slice[RIGHT]);
1197 feather dir = 1 , relx 0->1 : factor 0 -> 1
1198 feather dir = 0 , relx 0->1 : factor 1 -> 1
1199 feather dir = -1, relx 0->1 : factor 1 -> 0
1201 Real feather_factor = 1;
1202 if (feather_dir > 0)
1203 feather_factor = relx;
1204 else if (feather_dir < 0)
1205 feather_factor = 1 - relx;
1207 stem_y += feather_factor * beam_translation
1208 * beam_multiplicity[Direction(((french) ? DOWN : UP)*stem_dir)];
1209 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1210 - stem->relative_coordinate (common[Y_AXIS], Y_AXIS);
1212 return stem_y + id;
1216 Hmm. At this time, beam position and slope are determined. Maybe,
1217 stem directions and length should set to relative to the chord's
1218 position of the beam. */
1219 MAKE_SCHEME_CALLBACK (Beam, set_stem_lengths, 1);
1221 Beam::set_stem_lengths (SCM smob)
1223 Grob *me = unsmob_grob (smob);
1225 /* trigger callbacks. */
1226 (void) me->get_property ("direction");
1227 (void) me->get_property ("beaming");
1229 SCM posns = me->get_property ("positions");
1231 extract_grob_set (me, "stems", stems);
1232 if (!stems.size ())
1233 return posns;
1235 Grob *common[2];
1236 for (int a = 2; a--;)
1237 common[a] = common_refpoint_of_array (stems, me, Axis (a));
1239 Drul_array<Real> pos = ly_scm2realdrul (posns);
1240 Real staff_space = Staff_symbol_referencer::staff_space (me);
1241 scale_drul (&pos, staff_space);
1243 bool gap = false;
1244 Real thick = 0.0;
1245 if (robust_scm2int (me->get_property ("gap-count"), 0))
1247 gap = true;
1248 thick = get_thickness (me);
1251 Grob *fvs = first_normal_stem (me);
1252 Grob *lvs = last_normal_stem (me);
1254 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1255 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1256 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
1258 for (vsize i = 0; i < stems.size (); i++)
1260 Grob *s = stems[i];
1262 bool french = to_boolean (s->get_property ("french-beaming"));
1263 Real stem_y = calc_stem_y (me, s, common,
1264 xl, xr, feather_dir,
1265 pos, french && s != lvs && s!= fvs);
1268 Make the stems go up to the end of the beam. This doesn't matter
1269 for normal beams, but for tremolo beams it looks silly otherwise.
1271 if (gap
1272 && !Stem::is_invisible (s))
1273 stem_y += thick * 0.5 * get_grob_direction (s);
1276 Do set_stemend for invisible stems too, so tuplet brackets
1277 have a reference point for sloping
1279 Stem::set_stemend (s, 2 * stem_y / staff_space);
1282 return posns;
1285 void
1286 Beam::set_beaming (Grob *me, Beaming_pattern const *beaming)
1288 extract_grob_set (me, "stems", stems);
1290 Direction d = LEFT;
1291 for (vsize i = 0; i < stems.size (); i++)
1294 Don't overwrite user settings.
1298 Grob *stem = stems[i];
1299 SCM beaming_prop = stem->get_property ("beaming");
1300 if (beaming_prop == SCM_EOL
1301 || index_get_cell (beaming_prop, d) == SCM_EOL)
1303 int count = beaming->beamlet_count (i, d);
1304 if (i > 0
1305 && i + 1 < stems.size ()
1306 && Stem::is_invisible (stem))
1307 count = min (count, beaming->beamlet_count (i,-d));
1309 if ( ((i == 0 && d == LEFT)
1310 || (i == stems.size ()-1 && d == RIGHT))
1311 && stems.size () > 1
1312 && to_boolean (me->get_property ("clip-edges")))
1313 count = 0;
1315 Stem::set_beaming (stem, count, d);
1318 while (flip (&d) != LEFT);
1323 Beam::forced_stem_count (Grob *me)
1325 extract_grob_set (me, "normal-stems", stems);
1327 int f = 0;
1328 for (vsize i = 0; i < stems.size (); i++)
1330 Grob *s = stems[i];
1332 /* I can imagine counting those boundaries as a half forced stem,
1333 but let's count them full for now. */
1334 Direction defdir = to_dir (s->get_property ("default-direction"));
1336 if (abs (Stem::chord_start_y (s)) > 0.1
1337 && defdir
1338 && get_grob_direction (s) != defdir)
1339 f++;
1341 return f;
1345 Beam::normal_stem_count (Grob *me)
1347 extract_grob_set (me, "normal-stems", stems);
1348 return stems.size ();
1351 Grob *
1352 Beam::first_normal_stem (Grob *me)
1354 extract_grob_set (me, "normal-stems", stems);
1355 return stems.size () ? stems[0] : 0;
1358 Grob *
1359 Beam::last_normal_stem (Grob *me)
1361 extract_grob_set (me, "normal-stems", stems);
1362 return stems.size () ? stems.back () : 0;
1366 [TODO]
1368 handle rest under beam (do_post: beams are calculated now)
1369 what about combination of collisions and rest under beam.
1371 Should lookup
1373 rest -> stem -> beam -> interpolate_y_position ()
1375 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Beam, rest_collision_callback, 2, 1, "");
1377 Beam::rest_collision_callback (SCM smob, SCM prev_offset)
1379 Grob *rest = unsmob_grob (smob);
1380 if (scm_is_number (rest->get_property ("staff-position")))
1381 return scm_from_int (0);
1383 Real offset = robust_scm2double (prev_offset, 0.0);
1385 Grob *st = unsmob_grob (rest->get_object ("stem"));
1386 Grob *stem = st;
1387 if (!stem)
1388 return scm_from_double (0.0);
1389 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1390 if (!beam
1391 || !Beam::has_interface (beam)
1392 || !Beam::normal_stem_count (beam))
1393 return scm_from_double (0.0);
1395 Drul_array<Real> pos (robust_scm2drul (beam->get_property ("positions"),
1396 Drul_array<Real> (0,0)));
1398 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1400 scale_drul (&pos, staff_space);
1402 Real dy = pos[RIGHT] - pos[LEFT];
1404 Drul_array<Grob*> visible_stems (first_normal_stem (beam),
1405 last_normal_stem (beam));
1406 extract_grob_set (beam, "stems", stems);
1408 Grob *common = common_refpoint_of_array (stems, beam, X_AXIS);
1410 Real x0 = visible_stems[LEFT]->relative_coordinate (common, X_AXIS);
1411 Real dx = visible_stems[RIGHT]->relative_coordinate (common, X_AXIS) - x0;
1412 Real slope = dy && dx ? dy / dx : 0;
1414 Direction d = get_grob_direction (stem);
1415 Real stem_y = pos[LEFT]
1416 + (stem->relative_coordinate (common, X_AXIS) - x0) * slope;
1418 Real beam_translation = get_beam_translation (beam);
1419 Real beam_thickness = Beam::get_thickness (beam);
1422 TODO: this is not strictly correct for 16th knee beams.
1424 int beam_count
1425 = Stem::beam_multiplicity (stem).length () + 1;
1427 Real height_of_my_beams = beam_thickness / 2
1428 + (beam_count - 1) * beam_translation;
1429 Real beam_y = stem_y - d * height_of_my_beams;
1431 Grob *common_y = rest->common_refpoint (beam, Y_AXIS);
1434 TODO: this is dubious, because this call needs the info we're
1435 computing right now.
1437 Interval rest_extent = rest->extent (common_y, Y_AXIS);
1438 rest_extent.translate (offset);
1440 Real rest_dim = rest_extent[d];
1441 Real minimum_distance
1442 = staff_space * (robust_scm2double (stem->get_property ("stemlet-length"), 0.0)
1443 + robust_scm2double (rest->get_property ("minimum-distance"), 0.0));
1445 Real shift = d * min (d * (beam_y - d * minimum_distance - rest_dim), 0.0);
1447 shift /= staff_space;
1448 Real rad = Staff_symbol_referencer::line_count (rest) * staff_space / 2;
1450 /* Always move discretely by half spaces */
1451 shift = ceil (fabs (shift * 2.0)) / 2.0 * sign (shift);
1453 /* Inside staff, move by whole spaces*/
1454 if ((rest_extent[d] + staff_space * shift) * d
1455 < rad
1456 || (rest_extent[-d] + staff_space * shift) * -d
1457 < rad)
1458 shift = ceil (fabs (shift)) * sign (shift);
1460 return scm_from_double (offset + staff_space * shift);
1463 bool
1464 Beam::is_knee (Grob *me)
1466 SCM k = me->get_property ("knee");
1467 if (scm_is_bool (k))
1468 return ly_scm2bool (k);
1470 bool knee = false;
1471 int d = 0;
1472 extract_grob_set (me, "stems", stems);
1473 for (vsize i = stems.size (); i--;)
1475 Direction dir = get_grob_direction (stems[i]);
1476 if (d && d != dir)
1478 knee = true;
1479 break;
1481 d = dir;
1484 me->set_property ("knee", ly_bool2scm (knee));
1486 return knee;
1489 bool
1490 Beam::is_cross_staff (Grob *me)
1492 extract_grob_set (me, "stems", stems);
1493 Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
1494 for (vsize i = 0; i < stems.size (); i++)
1495 if (Staff_symbol_referencer::get_staff_symbol (stems[i]) != staff_symbol)
1496 return true;
1497 return false;
1500 MAKE_SCHEME_CALLBACK (Beam, calc_cross_staff, 1)
1502 Beam::calc_cross_staff (SCM smob)
1504 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1508 Beam::get_direction_beam_count (Grob *me, Direction d)
1510 extract_grob_set (me, "stems", stems);
1511 int bc = 0;
1513 for (vsize i = stems.size (); i--;)
1516 Should we take invisible stems into account?
1518 if (get_grob_direction (stems[i]) == d)
1519 bc = max (bc, (Stem::beam_multiplicity (stems[i]).length () + 1));
1522 return bc;
1525 ADD_INTERFACE (Beam,
1526 "A beam.\n"
1527 "\n"
1528 "The @code{thickness} property is the weight of beams,"
1529 " measured in staffspace. The @code{direction} property is"
1530 " not user-serviceable. Use the @code{direction} property"
1531 " of @code{Stem} instead.",
1533 /* properties */
1534 "annotation "
1535 "auto-knee-gap "
1536 "beamed-stem-shorten "
1537 "beaming "
1538 "break-overshoot "
1539 "clip-edges "
1540 "concaveness "
1541 "damping "
1542 "details "
1543 "direction "
1544 "gap "
1545 "gap-count "
1546 "grow-direction "
1547 "inspect-quants "
1548 "knee "
1549 "length-fraction "
1550 "least-squares-dy "
1551 "neutral-direction "
1552 "normal-stems "
1553 "positions "
1554 "quant-score "
1555 "quantized-positions "
1556 "shorten "
1557 "stems "
1558 "thickness "