2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
7 LilyPond is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 LilyPond is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
24 - Determine auto knees based on positions if it's set by the user.
26 - the code is littered with * and / staff_space calls for
27 #'positions. Consider moving to real-world coordinates?
29 Problematic issue is user tweaks (user tweaks are in staff-coordinates.)
33 - Stems run to the Y-center of the beam.
35 - beam_translation is the offset between Y centers of the beam.
40 #include "axis-group-interface.hh"
41 #include "align-interface.hh"
42 #include "beam-scoring-problem.hh"
43 #include "beaming-pattern.hh"
44 #include "directional-element-interface.hh"
45 #include "grob-array.hh"
46 #include "international.hh"
47 #include "interval-set.hh"
52 #include "note-head.hh"
53 #include "output-def.hh"
54 #include "pointer-group-interface.hh"
55 #include "rhythmic-head.hh"
57 #include "staff-symbol.hh"
58 #include "staff-symbol-referencer.hh"
62 #if DEBUG_BEAM_SCORING
63 #include "text-interface.hh" // debug output.
64 #include "font-interface.hh" // debug output.
69 Beam_stem_segment::Beam_stem_segment ()
71 max_connect_ = 1000; // infinity
81 beam_segment_less (Beam_segment const &a, Beam_segment const &b)
83 return a.horizontal_[LEFT] < b.horizontal_[LEFT];
86 Beam_segment::Beam_segment ()
92 Beam::add_stem (Grob *me, Grob *s)
94 if (Stem::get_beam (s))
96 programming_error ("Stem already has beam");
100 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
101 s->set_object ("beam", me->self_scm ());
102 add_bound_item (dynamic_cast<Spanner *> (me), dynamic_cast<Item *> (s));
106 Beam::get_beam_thickness (Grob *me)
108 return robust_scm2double (me->get_property ("beam-thickness"), 0)
109 * Staff_symbol_referencer::staff_space (me);
112 /* Return the translation between 2 adjoining beams. */
114 Beam::get_beam_translation (Grob *me)
116 int beam_count = get_beam_count (me);
117 Real staff_space = Staff_symbol_referencer::staff_space (me);
118 Real line = Staff_symbol_referencer::line_thickness (me);
119 Real beam_thickness = get_beam_thickness (me);
120 Real fract = robust_scm2double (me->get_property ("length-fraction"), 1.0);
122 Real beam_translation = beam_count < 4
123 ? (2 * staff_space + line - beam_thickness) / 2.0
124 : (3 * staff_space + line - beam_thickness) / 3.0;
126 return fract * beam_translation;
129 /* Maximum beam_count. */
131 Beam::get_beam_count (Grob *me)
135 extract_grob_set (me, "stems", stems);
136 for (vsize i = 0; i < stems.size (); i++)
138 Grob *stem = stems[i];
139 m = max (m, (Stem::beam_multiplicity (stem).length () + 1));
144 MAKE_SCHEME_CALLBACK (Beam, calc_normal_stems, 1);
146 Beam::calc_normal_stems (SCM smob)
148 Grob *me = Grob::unsmob (smob);
150 extract_grob_set (me, "stems", stems);
151 SCM val = Grob_array::make_array ();
152 Grob_array *ga = Grob_array::unsmob (val);
153 for (vsize i = 0; i < stems.size (); i++)
154 if (Stem::is_normal_stem (stems[i]))
160 MAKE_SCHEME_CALLBACK (Beam, calc_direction, 1);
162 Beam::calc_direction (SCM smob)
164 Grob *me = Grob::unsmob (smob);
166 /* Beams with less than 2 two stems don't make much sense, but could happen
173 Direction dir = CENTER;
175 int count = normal_stem_count (me);
178 extract_grob_set (me, "stems", stems);
179 if (stems.size () == 0)
181 me->warning (_ ("removing beam with no stems"));
184 return SCM_UNSPECIFIED;
188 Grob *stem = first_normal_stem (me);
191 This happens for chord tremolos.
196 if (is_direction (stem->get_property_data ("direction")))
197 dir = to_dir (stem->get_property_data ("direction"));
199 dir = to_dir (stem->get_property ("default-direction"));
201 extract_grob_set (stem, "note-heads", heads);
202 /* default position of Kievan heads with beams is down
203 placing this here avoids warnings downstream */
206 if (scm_is_eq (heads[0]->get_property ("style"),
207 ly_symbol2scm ("kievan")))
219 dir = get_default_dir (me);
221 consider_auto_knees (me);
226 set_stem_directions (me, dir);
229 return scm_from_int (dir);
232 /* We want a maximal number of shared beams, but if there is choice, we
233 * take the one that is closest to the end of the stem. This is for
245 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
249 Slice lslice = int_list_to_slice (scm_cdr (left_beaming));
253 for (int i = lslice[-left_dir];
254 (i - lslice[left_dir]) * left_dir <= 0; i += left_dir)
257 for (SCM s = scm_car (right_beaming); scm_is_pair (s); s = scm_cdr (s))
259 int k = -right_dir * scm_to_int (scm_car (s)) + i;
260 if (scm_is_true (scm_c_memq (scm_from_int (k), left_beaming)))
264 if (count >= best_count)
274 MAKE_SCHEME_CALLBACK (Beam, calc_beaming, 1)
276 Beam::calc_beaming (SCM smob)
278 Grob *me = Grob::unsmob (smob);
280 extract_grob_set (me, "stems", stems);
283 last_int.set_empty ();
285 SCM last_beaming = scm_cons (SCM_EOL, scm_list_1 (scm_from_int (0)));
286 Direction last_dir = CENTER;
287 for (vsize i = 0; i < stems.size (); i++)
289 Grob *this_stem = stems[i];
290 SCM this_beaming = this_stem->get_property ("beaming");
292 Direction this_dir = get_grob_direction (this_stem);
293 if (scm_is_pair (last_beaming) && scm_is_pair (this_beaming))
295 int start_point = position_with_maximal_common_beams
296 (last_beaming, this_beaming,
297 last_dir ? last_dir : this_dir,
301 for (LEFT_and_RIGHT (d))
303 new_slice.set_empty ();
304 SCM s = index_get_cell (this_beaming, d);
305 for (; scm_is_pair (s); s = scm_cdr (s))
308 = start_point - this_dir * scm_to_int (scm_car (s));
310 new_slice.add_point (new_beam_pos);
311 scm_set_car_x (s, scm_from_int (new_beam_pos));
315 if (!new_slice.is_empty ())
316 last_int = new_slice;
321 FIXME: what's this for?
323 SCM s = scm_cdr (this_beaming);
324 for (; scm_is_pair (s); s = scm_cdr (s))
326 int np = -this_dir * scm_to_int (scm_car (s));
327 scm_set_car_x (s, scm_from_int (np));
328 last_int.add_point (np);
332 if (scm_ilength (scm_cdr (this_beaming)) > 0)
334 last_beaming = this_beaming;
343 operator <(Beam_stem_segment const &a,
344 Beam_stem_segment const &b)
346 return a.rank_ < b.rank_;
349 typedef map<int, vector<Beam_stem_segment> > Position_stem_segments_map;
351 MAKE_SCHEME_CALLBACK (Beam, calc_beam_segments, 1);
353 Beam::calc_beam_segments (SCM smob)
355 /* ugh, this has a side-effect that we need to ensure that
356 Stem #'beaming is correct */
357 Grob *me_grob = Grob::unsmob (smob);
358 (void) me_grob->get_property ("beaming");
360 Spanner *me = dynamic_cast<Spanner *> (me_grob);
362 extract_grob_set (me, "stems", stems);
364 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
365 for (LEFT_and_RIGHT (d))
366 commonx = me->get_bound (d)->common_refpoint (commonx, X_AXIS);
368 int gap_count = robust_scm2int (me->get_property ("gap-count"), 0);
369 Real gap_length = robust_scm2double (me->get_property ("gap"), 0.0);
371 Position_stem_segments_map stem_segments;
372 Real lt = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
374 /* There are two concepts of "rank" that are used in the following code.
375 The beam_rank is the vertical position of the beam (larger numbers are
376 closer to the noteheads). Beam_stem_segment.rank_, on the other hand,
377 is the horizontal position of the segment (this is incremented by two
378 for each stem; the beam segment on the right side of the stem has
379 a higher rank (by one) than its neighbour to the left). */
381 for (vsize i = 0; i < stems.size (); i++)
383 Grob *stem = stems[i];
384 Real stem_width = robust_scm2double (stem->get_property ("thickness"), 1.0) * lt;
385 Real stem_x = stem->relative_coordinate (commonx, X_AXIS);
386 SCM beaming = stem->get_property ("beaming");
388 for (LEFT_and_RIGHT (d))
390 // Find the maximum and minimum beam ranks.
391 // Given that RANKS is never reset to empty, the interval will always be
392 // smallest for the left beamlet of the first stem, and then it might grow.
393 // Do we really want this? (It only affects the tremolo gaps) --jneem
394 for (SCM s = index_get_cell (beaming, d);
395 scm_is_pair (s); s = scm_cdr (s))
397 if (!scm_is_integer (scm_car (s)))
400 int beam_rank = scm_to_int (scm_car (s));
401 ranks.add_point (beam_rank);
404 for (SCM s = index_get_cell (beaming, d);
405 scm_is_pair (s); s = scm_cdr (s))
407 if (!scm_is_integer (scm_car (s)))
410 int beam_rank = scm_to_int (scm_car (s));
411 Beam_stem_segment seg;
413 seg.stem_x_ = stem_x;
414 seg.rank_ = 2 * i + (d + 1) / 2;
415 seg.width_ = stem_width;
418 seg.max_connect_ = robust_scm2int (stem->get_property ("max-beam-connect"), 1000);
420 Direction stem_dir = get_grob_direction (stem);
423 = (stem_dir * beam_rank < (stem_dir * ranks[-stem_dir] + gap_count));
424 stem_segments[beam_rank].push_back (seg);
429 Drul_array<Real> break_overshoot
430 = robust_scm2drul (me->get_property ("break-overshoot"),
431 Drul_array<Real> (-0.5, 0.0));
433 vector<Beam_segment> segments;
434 for (Position_stem_segments_map::const_iterator i (stem_segments.begin ());
435 i != stem_segments.end (); i++)
437 vector<Beam_stem_segment> segs = (*i).second;
438 vector_sort (segs, less<Beam_stem_segment> ());
440 Beam_segment current;
442 // Iterate over all of the segments of the current beam rank,
443 // merging the adjacent Beam_stem_segments into one Beam_segment
445 int vertical_count = (*i).first;
446 for (vsize j = 0; j < segs.size (); j++)
448 // Keeping track of the different directions here is a little tricky.
449 // segs[j].dir_ is the direction of the beam segment relative to the stem
450 // (ie. segs[j].dir_ == LEFT if the beam segment sticks out to the left of
451 // its stem) whereas event_dir refers to the edge of the beam segment that
452 // we are currently looking at (ie. if segs[j].dir_ == event_dir then we
453 // are looking at that edge of the beam segment that is furthest from its
455 Beam_stem_segment const &seg = segs[j];
456 for (LEFT_and_RIGHT (event_dir))
458 Beam_stem_segment const &neighbor_seg = segs[j + event_dir];
459 // TODO: make names clearer? --jneem
460 // on_line_bound: whether the current segment is on the boundary of the WHOLE beam
461 // on_beam_bound: whether the current segment is on the boundary of just that part
462 // of the beam with the current beam_rank
463 bool on_line_bound = (seg.dir_ == LEFT) ? seg.stem_index_ == 0
464 : seg.stem_index_ == stems.size () - 1;
465 bool on_beam_bound = (event_dir == LEFT) ? j == 0
466 : j == segs.size () - 1;
467 bool inside_stem = (event_dir == LEFT)
468 ? seg.stem_index_ > 0
469 : seg.stem_index_ + 1 < stems.size ();
471 bool event = on_beam_bound
472 || abs (seg.rank_ - neighbor_seg.rank_) > 1
473 || (abs (vertical_count) >= seg.max_connect_
474 || abs (vertical_count) >= neighbor_seg.max_connect_);
477 // Then this edge of the current segment is irrelevant because it will
478 // be connected with the next segment in the event_dir direction.
479 // If we skip the left edge here, the right edge of
480 // the previous segment has already been skipped since
481 // the conditions are symmetric
484 current.vertical_count_ = vertical_count;
485 current.horizontal_[event_dir] = seg.stem_x_;
486 if (seg.dir_ == event_dir)
487 // then we are examining the edge of a beam segment that is furthest
491 && me->get_bound (event_dir)->break_status_dir ())
493 current.horizontal_[event_dir]
494 = (Axis_group_interface::generic_bound_extent (me->get_bound (event_dir),
495 commonx, X_AXIS)[RIGHT]
496 + event_dir * break_overshoot[event_dir]);
500 Grob *stem = stems[seg.stem_index_];
501 Drul_array<Real> beamlet_length
502 = robust_scm2interval (stem->get_property ("beamlet-default-length"), Interval (1.1, 1.1));
503 Drul_array<Real> max_proportion
504 = robust_scm2interval (stem->get_property ("beamlet-max-length-proportion"), Interval (0.75, 0.75));
505 Real length = beamlet_length[seg.dir_];
509 Grob *neighbor_stem = stems[seg.stem_index_ + event_dir];
510 Real neighbor_stem_x = neighbor_stem->relative_coordinate (commonx, X_AXIS);
512 length = min (length,
513 fabs (neighbor_stem_x - seg.stem_x_) * max_proportion[seg.dir_]);
515 current.horizontal_[event_dir] += event_dir * length;
519 // we are examining the edge of a beam segment that is closest
520 // (ie. touching, unless there is a gap) its stem.
522 current.horizontal_[event_dir] += event_dir * seg.width_ / 2;
525 current.horizontal_[event_dir] -= event_dir * gap_length;
527 if (Stem::is_invisible (seg.stem_))
530 Need to do this in case of whole notes. We don't want the
531 heads to collide with the beams.
533 extract_grob_set (seg.stem_, "note-heads", heads);
535 for (vsize k = 0; k < heads.size (); k++)
536 current.horizontal_[event_dir]
537 = event_dir * min (event_dir * current.horizontal_[event_dir],
540 * heads[k]->extent (commonx,
541 X_AXIS)[-event_dir]);
546 if (event_dir == RIGHT)
548 segments.push_back (current);
549 current = Beam_segment ();
556 SCM segments_scm = SCM_EOL;
558 for (vsize i = segments.size (); i--;)
560 segments_scm = scm_cons (scm_list_2 (scm_cons (ly_symbol2scm ("vertical-count"),
561 scm_from_int (segments[i].vertical_count_)),
562 scm_cons (ly_symbol2scm ("horizontal"),
563 ly_interval2scm (segments[i].horizontal_))),
570 MAKE_SCHEME_CALLBACK (Beam, calc_x_positions, 1);
572 Beam::calc_x_positions (SCM smob)
574 Spanner *me = Spanner::unsmob (smob);
575 SCM segments = me->get_property ("beam-segments");
576 Interval x_positions;
577 x_positions.set_empty ();
578 for (SCM s = segments; scm_is_pair (s); s = scm_cdr (s))
579 x_positions.unite (robust_scm2interval (ly_assoc_get (ly_symbol2scm ("horizontal"),
582 Interval (0.0, 0.0)));
584 // Case for beams without segments (i.e. uniting two skips with a beam)
585 // TODO: should issue a warning? warning likely issued downstream, but couldn't hurt...
586 if (x_positions.is_empty ())
588 extract_grob_set (me, "stems", stems);
589 Grob *common_x = common_refpoint_of_array (stems, me, X_AXIS);
590 for (LEFT_and_RIGHT (d))
591 x_positions[d] = me->relative_coordinate (common_x, X_AXIS);
593 return ly_interval2scm (x_positions);
597 Beam::get_beam_segments (Grob *me)
599 SCM segments_scm = me->get_property ("beam-segments");
600 vector<Beam_segment> segments;
601 for (SCM s = segments_scm; scm_is_pair (s); s = scm_cdr (s))
603 segments.push_back (Beam_segment ());
604 segments.back ().vertical_count_ = robust_scm2int (ly_assoc_get (ly_symbol2scm ("vertical-count"), scm_car (s), SCM_EOL), 0);
605 segments.back ().horizontal_ = robust_scm2interval (ly_assoc_get (ly_symbol2scm ("horizontal"), scm_car (s), SCM_EOL), Interval (0.0, 0.0));
611 MAKE_SCHEME_CALLBACK (Beam, print, 1);
613 Beam::print (SCM grob)
615 Spanner *me = Spanner::unsmob (grob);
617 TODO - mild code dup for all the commonx calls.
618 Some use just common_refpoint_of_array, some (in print and
619 calc_beam_segments) use this plus calls to get_bound.
621 Figure out if there is any particular reason for this and
622 consolidate in one Beam::get_common function.
624 extract_grob_set (me, "stems", stems);
625 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
626 for (LEFT_and_RIGHT (d))
627 commonx = me->get_bound (d)->common_refpoint (commonx, X_AXIS);
629 vector<Beam_segment> segments = get_beam_segments (me);
631 if (!segments.size ())
634 Real blot = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
636 SCM posns = me->get_property ("quantized-positions");
637 Interval span = robust_scm2interval (me->get_property ("X-positions"), Interval (0, 0));
639 if (!is_number_pair (posns))
641 programming_error ("no beam positions?");
642 pos = Interval (0, 0);
645 pos = ly_scm2realdrul (posns);
647 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
649 Real dy = pos[RIGHT] - pos[LEFT];
650 Real slope = (dy && span.length ()) ? dy / span.length () : 0;
652 Real beam_thickness = get_beam_thickness (me);
653 Real beam_dy = get_beam_translation (me);
655 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
657 Interval placements = robust_scm2interval (me->get_property ("normalized-endpoints"), Interval (0.0, 0.0));
660 vsize extreme = (segments[0].vertical_count_ == 0
661 ? segments[0].vertical_count_
662 : segments.back ().vertical_count_);
664 for (vsize i = 0; i < segments.size (); i++)
666 Real local_slope = slope;
668 Makes local slope proportional to the ratio of the length of this beam
672 local_slope += (feather_dir * segments[i].vertical_count_
674 * placements.length ()
677 Stencil b = Lookup::beam (local_slope, segments[i].horizontal_.length (), beam_thickness, blot);
679 b.translate_axis (segments[i].horizontal_[LEFT], X_AXIS);
680 Real multiplier = feather_dir ? placements[LEFT] : 1.0;
682 Interval weights (1 - multiplier, multiplier);
684 if (feather_dir != LEFT)
687 // we need two translations: the normal one and
688 // the one of the lowest segment
689 size_t idx[] = {i, extreme};
690 Real translations[2];
692 for (int j = 0; j < 2; j++)
693 translations[j] = slope
694 * (segments[idx[j]].horizontal_[LEFT] - span.linear_combination (CENTER))
695 + pos.linear_combination (CENTER)
696 + beam_dy * segments[idx[j]].vertical_count_;
698 Real weighted_average = translations[0] * weights[LEFT] + translations[1] * weights[RIGHT];
701 Tricky. The manipulation of the variable `weighted_average' below ensures
702 that beams with a RIGHT grow direction will start from the position of the
703 lowest segment at 0, and this error will decrease and decrease over the
704 course of the beam. Something with a LEFT grow direction, on the other
705 hand, will always start in the correct place but progressively accrue
706 error at broken places. This code shifts beams up given where they are
707 in the total span length (controlled by the variable `multiplier'). To
708 better understand what it does, try commenting it out: you'll see that
709 all of the RIGHT growing beams immediately start too low and get better
710 over line breaks, whereas all of the LEFT growing beams start just right
711 and get worse over line breaks.
713 Real factor = Interval (multiplier, 1 - multiplier).linear_combination (feather_dir);
715 if (segments[0].vertical_count_ < 0 && feather_dir)
716 weighted_average += beam_dy * (segments.size () - 1) * factor;
718 b.translate_axis (weighted_average, Y_AXIS);
720 the_beam.add_stencil (b);
724 #if (DEBUG_BEAM_SCORING)
725 SCM annotation = me->get_property ("annotation");
726 if (scm_is_string (annotation))
728 extract_grob_set (me, "stems", stems);
731 This code prints the demerits for each beam. Perhaps this
732 should be switchable for those who want to twiddle with the
736 SCM properties = Font_interface::text_font_alist_chain (me);
738 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-5), SCM_EOL),
741 Direction stem_dir = stems.size () ? to_dir (stems[0]->get_property ("direction")) : UP;
743 Stencil score = *Stencil::unsmob (Text_interface::interpret_markup
744 (me->layout ()->self_scm (), properties, annotation));
746 if (!score.is_empty ())
748 score.translate_axis (me->relative_coordinate (commonx, X_AXIS), X_AXIS);
749 the_beam.add_at_edge (Y_AXIS, stem_dir, score, 1.0);
754 the_beam.translate_axis (-me->relative_coordinate (commonx, X_AXIS), X_AXIS);
755 return the_beam.smobbed_copy ();
759 Beam::get_default_dir (Grob *me)
761 extract_grob_set (me, "stems", stems);
763 Drul_array<Real> extremes (0.0, 0.0);
764 for (iterof (s, stems); s != stems.end (); s++)
766 Interval positions = Stem::head_positions (*s);
767 for (DOWN_and_UP (d))
769 if (sign (positions[d]) == d)
770 extremes[d] = d * max (d * positions[d], d * extremes[d]);
774 Drul_array<int> total (0, 0);
775 Drul_array<int> count (0, 0);
777 bool force_dir = false;
778 for (vsize i = 0; i < stems.size (); i++)
781 Direction stem_dir = CENTER;
782 SCM stem_dir_scm = s->get_property_data ("direction");
783 if (is_direction (stem_dir_scm))
785 stem_dir = to_dir (stem_dir_scm);
789 stem_dir = to_dir (s->get_property ("default-direction"));
792 stem_dir = to_dir (s->get_property ("neutral-direction"));
797 total[stem_dir] += max (int (- stem_dir * Stem::head_positions (s) [-stem_dir]), 0);
803 if (abs (extremes[UP]) > -extremes[DOWN])
805 else if (extremes[UP] < -extremes[DOWN])
809 Direction dir = CENTER;
810 Direction d = CENTER;
811 if ((d = (Direction) sign (count[UP] - count[DOWN])))
815 && (d = (Direction) sign (total[UP] / count[UP] - total[DOWN] / count[DOWN])))
817 else if ((d = (Direction) sign (total[UP] - total[DOWN])))
820 dir = to_dir (me->get_property ("neutral-direction"));
825 /* Set all stems with non-forced direction to beam direction.
826 Urg: non-forced should become `without/with unforced' direction,
827 once stem gets cleaned-up. */
829 Beam::set_stem_directions (Grob *me, Direction d)
831 extract_grob_set (me, "stems", stems);
833 for (vsize i = 0; i < stems.size (); i++)
837 SCM forcedir = s->get_property_data ("direction");
838 if (!to_dir (forcedir))
839 set_grob_direction (s, d);
844 Only try horizontal beams for knees. No reliable detection of
845 anything else is possible here, since we don't know funky-beaming
846 settings, or X-distances (slopes!) People that want sloped
847 knee-beams, should set the directions manually.
852 this routine should take into account the stemlength scoring
853 of a possible knee/nonknee beam.
856 Beam::consider_auto_knees (Grob *me)
858 SCM scm = me->get_property ("auto-knee-gap");
859 if (!scm_is_number (scm))
862 vector<Interval> forbidden_intervals;
864 extract_grob_set (me, "normal-stems", stems);
866 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
867 Real staff_space = Staff_symbol_referencer::staff_space (me);
869 vector<Interval> head_extents_array;
870 for (vsize i = 0; i < stems.size (); i++)
872 Grob *stem = stems[i];
874 Interval head_extents;
875 if (Stem::head_count (stem))
877 head_extents = Stem::head_positions (stem);
878 head_extents.widen (1);
879 head_extents *= staff_space * 0.5;
882 We could subtract beam Y position, but this routine only
883 sets stem directions, a constant shift does not have an
886 head_extents += stem->pure_relative_y_coordinate (common, 0, INT_MAX);
888 if (to_dir (stem->get_property_data ("direction")))
890 Direction stemdir = to_dir (stem->get_property ("direction"));
891 head_extents[-stemdir] = -stemdir * infinity_f;
894 head_extents_array.push_back (head_extents);
896 forbidden_intervals.push_back (head_extents);
900 Real max_gap_len = 0.0;
902 vector<Interval> allowed_regions
903 = Interval_set::interval_union (forbidden_intervals).complement ().intervals ();
904 for (vsize i = allowed_regions.size () - 1; i != VPOS; i--)
906 Interval gap = allowed_regions[i];
909 the outer gaps are not knees.
911 if (isinf (gap[LEFT]) || isinf (gap[RIGHT]))
914 if (gap.length () >= max_gap_len)
916 max_gap_len = gap.length ();
921 Real beam_translation = get_beam_translation (me);
922 Real beam_thickness = Beam::get_beam_thickness (me);
923 int beam_count = Beam::get_beam_count (me);
924 Real height_of_beams = beam_thickness / 2
925 + (beam_count - 1) * beam_translation;
926 Real threshold = scm_to_double (scm) + height_of_beams;
928 if (max_gap_len > threshold)
931 for (vsize i = 0; i < stems.size (); i++)
933 Grob *stem = stems[i];
934 Interval head_extents = head_extents_array[j++];
936 Direction d = (head_extents.center () < max_gap.center ())
939 stem->set_property ("direction", scm_from_int (d));
941 head_extents.intersect (max_gap);
942 assert (head_extents.is_empty () || head_extents.length () < 1e-6);
947 MAKE_SCHEME_CALLBACK (Beam, calc_stem_shorten, 1)
949 Beam::calc_stem_shorten (SCM smob)
951 Grob *me = Grob::unsmob (smob);
954 shortening looks silly for x staff beams
957 return scm_from_int (0);
959 Real forced_fraction = 1.0 * forced_stem_count (me)
960 / normal_stem_count (me);
962 int beam_count = get_beam_count (me);
964 SCM shorten_list = me->get_property ("beamed-stem-shorten");
965 if (scm_is_null (shorten_list))
966 return scm_from_int (0);
968 Real staff_space = Staff_symbol_referencer::staff_space (me);
971 = robust_list_ref (beam_count - 1, shorten_list);
972 Real shorten = scm_to_double (shorten_elt) * staff_space;
974 shorten *= forced_fraction;
977 return scm_from_double (shorten);
979 return scm_from_double (0.0);
982 MAKE_SCHEME_CALLBACK (Beam, quanting, 3);
984 Beam::quanting (SCM smob, SCM ys_scm, SCM align_broken_intos)
986 Grob *me = Grob::unsmob (smob);
987 Drul_array<Real> ys = robust_scm2drul (ys_scm, Drul_array<Real> (infinity_f, -infinity_f));
988 bool cbs = to_boolean (align_broken_intos);
990 Beam_scoring_problem problem (me, ys, cbs);
991 ys = problem.solve ();
993 return ly_interval2scm (ys);
997 Report slice containing the numbers that are both in (car BEAMING)
1001 where_are_the_whole_beams (SCM beaming)
1005 for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
1007 if (scm_is_true (scm_c_memq (scm_car (s), scm_cdr (beaming))))
1009 l.add_point (scm_to_int (scm_car (s)));
1015 /* Return the Y position of the stem-end, given the Y-left, Y-right
1016 in POS for stem S. This Y position is relative to S. */
1018 Beam::calc_stem_y (Grob *me, Grob *stem, Grob **common,
1019 Real xl, Real xr, Direction feather_dir,
1020 Drul_array<Real> pos, bool french)
1022 Real beam_translation = get_beam_translation (me);
1023 Direction stem_dir = get_grob_direction (stem);
1026 Real relx = dx ? (stem->relative_coordinate (common[X_AXIS], X_AXIS) - xl) / dx : 0;
1027 Real xdir = 2 * relx - 1;
1029 Real stem_y = linear_combination (pos, xdir);
1031 SCM beaming = stem->get_property ("beaming");
1033 Slice beam_slice (french
1034 ? where_are_the_whole_beams (beaming)
1035 : Stem::beam_multiplicity (stem));
1036 if (beam_slice.is_empty ())
1037 beam_slice = Slice (0, 0);
1038 Interval beam_multiplicity (beam_slice[LEFT],
1042 feather dir = 1 , relx 0->1 : factor 0 -> 1
1043 feather dir = 0 , relx 0->1 : factor 1 -> 1
1044 feather dir = -1, relx 0->1 : factor 1 -> 0
1046 Real feather_factor = 1;
1047 if (feather_dir > 0)
1048 feather_factor = relx;
1049 else if (feather_dir < 0)
1050 feather_factor = 1 - relx;
1052 stem_y += feather_factor * beam_translation
1053 * beam_multiplicity[Direction (((french) ? DOWN : UP) * stem_dir)];
1054 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1055 - stem->relative_coordinate (common[Y_AXIS], Y_AXIS);
1061 Hmm. At this time, beam position and slope are determined. Maybe,
1062 stem directions and length should set to relative to the chord's
1063 position of the beam. */
1064 MAKE_SCHEME_CALLBACK (Beam, set_stem_lengths, 1);
1066 Beam::set_stem_lengths (SCM smob)
1068 Grob *me = Grob::unsmob (smob);
1070 /* trigger callbacks. */
1071 (void) me->get_property ("direction");
1072 (void) me->get_property ("beaming");
1074 SCM posns = me->get_property ("positions");
1076 extract_grob_set (me, "stems", stems);
1081 for (int a = 2; a--;)
1082 common[a] = common_refpoint_of_array (stems, me, Axis (a));
1084 Drul_array<Real> pos = ly_scm2realdrul (posns);
1085 Real staff_space = Staff_symbol_referencer::staff_space (me);
1086 scale_drul (&pos, staff_space);
1090 if (robust_scm2int (me->get_property ("gap-count"), 0))
1093 thick = get_beam_thickness (me);
1096 Grob *fvs = first_normal_stem (me);
1097 Grob *lvs = last_normal_stem (me);
1099 Interval x_span = robust_scm2interval (me->get_property ("X-positions"), Interval (0, 0));
1100 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
1102 for (vsize i = 0; i < stems.size (); i++)
1106 bool french = to_boolean (s->get_property ("french-beaming"));
1107 Real stem_y = calc_stem_y (me, s, common,
1108 x_span[LEFT], x_span[RIGHT], feather_dir,
1109 pos, french && s != lvs && s != fvs);
1112 Make the stems go up to the end of the beam. This doesn't matter
1113 for normal beams, but for tremolo beams it looks silly otherwise.
1116 && !Stem::is_invisible (s))
1117 stem_y += thick * 0.5 * get_grob_direction (s);
1120 Do set_stem_positions for invisible stems too, so tuplet brackets
1121 have a reference point for sloping
1123 Stem::set_stem_positions (s, 2 * stem_y / staff_space);
1130 Beam::set_beaming (Grob *me, Beaming_pattern const *beaming)
1132 extract_grob_set (me, "stems", stems);
1134 for (vsize i = 0; i < stems.size (); i++)
1137 Don't overwrite user settings.
1139 for (LEFT_and_RIGHT (d))
1141 Grob *stem = stems[i];
1142 SCM beaming_prop = stem->get_property ("beaming");
1143 if (scm_is_null (beaming_prop)
1144 || scm_is_null (index_get_cell (beaming_prop, d)))
1146 int count = beaming->beamlet_count (i, d);
1148 && i + 1 < stems.size ()
1149 && Stem::is_invisible (stem))
1150 count = min (count, beaming->beamlet_count (i, -d));
1152 if ( ((i == 0 && d == LEFT)
1153 || (i == stems.size () - 1 && d == RIGHT))
1154 && stems.size () > 1
1155 && to_boolean (me->get_property ("clip-edges")))
1158 Stem::set_beaming (stem, count, d);
1165 Beam::forced_stem_count (Grob *me)
1167 extract_grob_set (me, "normal-stems", stems);
1170 for (vsize i = 0; i < stems.size (); i++)
1174 /* I can imagine counting those boundaries as a half forced stem,
1175 but let's count them full for now. */
1176 Direction defdir = to_dir (s->get_property ("default-direction"));
1178 if (abs (Stem::chord_start_y (s)) > 0.1
1180 && get_grob_direction (s) != defdir)
1187 Beam::normal_stem_count (Grob *me)
1189 extract_grob_set (me, "normal-stems", stems);
1190 return stems.size ();
1194 Beam::first_normal_stem (Grob *me)
1196 extract_grob_set (me, "normal-stems", stems);
1197 return stems.size () ? stems[0] : 0;
1201 Beam::last_normal_stem (Grob *me)
1203 extract_grob_set (me, "normal-stems", stems);
1204 return stems.size () ? stems.back () : 0;
1210 handle rest under beam (do_post: beams are calculated now)
1211 what about combination of collisions and rest under beam.
1215 rest -> stem -> beam -> interpolate_y_position ()
1217 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Beam, rest_collision_callback, 2, 1, "");
1219 Beam::rest_collision_callback (SCM smob, SCM prev_offset)
1221 Grob *rest = Grob::unsmob (smob);
1222 if (scm_is_number (rest->get_property ("staff-position")))
1223 return scm_from_int (0);
1225 Real offset = robust_scm2double (prev_offset, 0.0);
1227 Grob *st = Grob::unsmob (rest->get_object ("stem"));
1230 return scm_from_double (0.0);
1231 Grob *beam = Grob::unsmob (stem->get_object ("beam"));
1233 || !Beam::has_interface (beam)
1234 || !Beam::normal_stem_count (beam))
1235 return scm_from_double (0.0);
1237 Grob *common_y = rest->common_refpoint (beam, Y_AXIS);
1239 Drul_array<Real> pos (robust_scm2drul (beam->get_property ("positions"),
1240 Drul_array<Real> (0, 0)));
1242 for (LEFT_and_RIGHT (dir))
1243 pos[dir] += beam->relative_coordinate (common_y, Y_AXIS);
1245 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1247 scale_drul (&pos, staff_space);
1249 Real dy = pos[RIGHT] - pos[LEFT];
1251 extract_grob_set (beam, "stems", stems);
1252 Grob *common = common_refpoint_of_array (stems, beam, X_AXIS);
1254 Interval x_span = robust_scm2interval (beam->get_property ("X-positions"),
1255 Interval (0.0, 0.0));
1256 Real x0 = x_span[LEFT];
1257 Real dx = x_span.length ();
1258 Real slope = dy && dx ? dy / dx : 0;
1260 Direction d = get_grob_direction (stem);
1261 Real stem_y = pos[LEFT]
1262 + (stem->relative_coordinate (common, X_AXIS) - x0) * slope;
1264 Real beam_translation = get_beam_translation (beam);
1265 Real beam_thickness = Beam::get_beam_thickness (beam);
1268 TODO: this is not strictly correct for 16th knee beams.
1271 = Stem::beam_multiplicity (stem).length () + 1;
1273 Real height_of_my_beams = beam_thickness / 2
1274 + (beam_count - 1) * beam_translation;
1275 Real beam_y = stem_y - d * height_of_my_beams;
1277 Interval rest_extent = rest->extent (rest, Y_AXIS);
1278 rest_extent.translate (offset + rest->get_parent (Y_AXIS)->relative_coordinate (common_y, Y_AXIS));
1280 Real rest_dim = rest_extent[d];
1281 Real minimum_distance
1282 = staff_space * (robust_scm2double (stem->get_property ("stemlet-length"), 0.0)
1283 + robust_scm2double (rest->get_property ("minimum-distance"), 0.0));
1285 Real shift = d * min (d * (beam_y - d * minimum_distance - rest_dim), 0.0);
1287 shift /= staff_space;
1289 /* Always move discretely by half spaces */
1290 shift = ceil (fabs (shift * 2.0)) / 2.0 * sign (shift);
1292 Interval staff_span = Staff_symbol_referencer::staff_span (rest);
1293 staff_span *= staff_space / 2;
1295 /* Inside staff, move by whole spaces*/
1296 if (staff_span.contains (rest_extent[d] + staff_space * shift)
1297 || staff_span.contains (rest_extent[-d] + staff_space * shift))
1298 shift = ceil (fabs (shift)) * sign (shift);
1300 return scm_from_double (offset + staff_space * shift);
1304 Estimate the position of a rest under a beam,
1305 using the average position of its neighboring heads.
1307 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Beam, pure_rest_collision_callback, 4, 1, "");
1309 Beam::pure_rest_collision_callback (SCM smob,
1314 Real previous = robust_scm2double (prev_offset, 0.0);
1316 Grob *me = Grob::unsmob (smob);
1317 Grob *stem = Grob::unsmob (me->get_object ("stem"));
1319 return scm_from_double (previous);
1320 Grob *beam = Grob::unsmob (stem->get_object ("beam"));
1322 || !Beam::normal_stem_count (beam)
1323 || !is_direction (beam->get_property_data ("direction")))
1324 return scm_from_double (previous);
1326 Real ss = Staff_symbol_referencer::staff_space (me);
1328 extract_grob_set (beam, "stems", stems);
1329 vector<Grob *> my_stems;
1331 for (vsize i = 0; i < stems.size (); i++)
1332 if (Stem::head_count (stems[i]) || stems[i] == stem)
1333 my_stems.push_back (stems[i]);
1337 for (vsize i = 0; i < my_stems.size (); i++)
1338 if (my_stems[i] == stem)
1346 if (idx == (vsize) - 1 || my_stems.size () == 1)
1347 return scm_from_double (previous);
1349 left = right = my_stems[1];
1350 else if (idx == my_stems.size () - 1)
1351 left = right = my_stems[idx - 1];
1354 left = my_stems[idx - 1];
1355 right = my_stems[idx + 1];
1358 /* Estimate the closest beam to be four positions away from the heads, */
1359 Direction beamdir = get_grob_direction (beam);
1360 Real beam_pos = (Stem::head_positions (left)[beamdir]
1361 + Stem::head_positions (right)[beamdir]) / 2.0
1362 + 4.0 * beamdir; // four staff-positions
1363 /* and that the closest beam never crosses staff center by more than two positions */
1364 beam_pos = max (-2.0, beam_pos * beamdir) * beamdir;
1366 Real minimum_distance
1367 = ss * (robust_scm2double (stem->get_property ("stemlet-length"), 0.0)
1368 + robust_scm2double (me->get_property ("minimum-distance"), 0.0));
1369 Real offset = beam_pos * ss / 2.0
1370 - minimum_distance * beamdir
1371 - me->extent (me, Y_AXIS)[beamdir];
1373 /* Always move by a whole number of staff spaces, always away from the beam */
1374 offset = floor (min (0.0, (offset - previous) / ss * beamdir))
1375 * ss * beamdir + previous;
1377 return scm_from_double (offset);
1381 Beam::is_knee (Grob *me)
1383 SCM k = me->get_property ("knee");
1384 if (scm_is_bool (k))
1385 return ly_scm2bool (k);
1389 extract_grob_set (me, "stems", stems);
1390 for (vsize i = stems.size (); i--;)
1392 Direction dir = get_grob_direction (stems[i]);
1401 me->set_property ("knee", ly_bool2scm (knee));
1407 Beam::is_cross_staff (Grob *me)
1409 extract_grob_set (me, "stems", stems);
1410 Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
1411 for (vsize i = 0; i < stems.size (); i++)
1412 if (Staff_symbol_referencer::get_staff_symbol (stems[i]) != staff_symbol)
1417 MAKE_SCHEME_CALLBACK (Beam, calc_cross_staff, 1)
1419 Beam::calc_cross_staff (SCM smob)
1421 return scm_from_bool (is_cross_staff (Grob::unsmob (smob)));
1425 Beam::get_direction_beam_count (Grob *me, Direction d)
1427 extract_grob_set (me, "stems", stems);
1430 for (vsize i = stems.size (); i--;)
1433 Should we take invisible stems into account?
1435 if (get_grob_direction (stems[i]) == d)
1436 bc = max (bc, (Stem::beam_multiplicity (stems[i]).length () + 1));
1442 ADD_INTERFACE (Beam,
1445 "The @code{beam-thickness} property is the weight of beams,"
1446 " measured in staffspace. The @code{direction} property is"
1447 " not user-serviceable. Use the @code{direction} property"
1448 " of @code{Stem} instead.\n"
1449 "The following properties may be set in the @code{details}"
1453 "@item stem-length-demerit-factor\n"
1454 "Demerit factor used for inappropriate stem lengths.\n"
1455 "@item secondary-beam-demerit\n"
1456 "Demerit used in quanting calculations for multiple"
1458 "@item region-size\n"
1459 "Size of region for checking quant scores.\n"
1461 "Epsilon for beam quant code to check for presence"
1463 "@item stem-length-limit-penalty\n"
1464 "Penalty for differences in stem lengths on a beam.\n"
1465 "@item damping-direction-penalty\n"
1466 "Demerit penalty applied when beam direction is different"
1467 " from damping direction.\n"
1468 "@item hint-direction-penalty\n"
1469 "Demerit penalty applied when beam direction is different"
1470 " from damping direction, but damping slope is"
1471 " <= @code{round-to-zero-slope}.\n"
1472 "@item musical-direction-factor\n"
1473 "Demerit scaling factor for difference between"
1474 " beam slope and music slope.\n"
1475 "@item ideal-slope-factor\n"
1476 "Demerit scaling factor for difference between"
1477 " beam slope and damping slope.\n"
1478 "@item round-to-zero-slope\n"
1479 "Damping slope which is considered zero for purposes of"
1480 " calculating direction penalties.\n"
1486 "beamed-stem-shorten "
1493 "collision-interfaces "
1494 "collision-voice-only "
1506 "neutral-direction "
1509 "quantized-positions "