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.
73 Beam_stem_segment::Beam_stem_segment ()
75 max_connect_ = 1000; // infinity
85 beam_segment_less (Beam_segment const &a, Beam_segment const &b)
87 return a.horizontal_[LEFT] < b.horizontal_[LEFT];
90 Beam_segment::Beam_segment ()
96 Beam::add_stem (Grob *me, Grob *s)
98 if (Stem::get_beam (s))
100 programming_error ("Stem already has beam");
104 Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
105 s->set_object ("beam", me->self_scm ());
106 add_bound_item (dynamic_cast<Spanner *> (me), dynamic_cast<Item *> (s));
110 Beam::get_beam_thickness (Grob *me)
112 return robust_scm2double (me->get_property ("beam-thickness"), 0)
113 * Staff_symbol_referencer::staff_space (me);
116 /* Return the translation between 2 adjoining beams. */
118 Beam::get_beam_translation (Grob *me)
120 int beam_count = get_beam_count (me);
121 Real staff_space = Staff_symbol_referencer::staff_space (me);
122 Real line = Staff_symbol_referencer::line_thickness (me);
123 Real beam_thickness = get_beam_thickness (me);
124 Real fract = robust_scm2double (me->get_property ("length-fraction"), 1.0);
127 if fract != 1.0, as is the case for grace notes, we want the gap
128 to decrease too. To achieve this, we divide the thickness by
130 return (beam_count < 4
131 ? (2 * staff_space * fract + line * fract - beam_thickness) / 2.0
132 : (3 * staff_space * fract + line * fract - beam_thickness) / 3.0);
135 /* Maximum beam_count. */
137 Beam::get_beam_count (Grob *me)
141 extract_grob_set (me, "stems", stems);
142 for (vsize i = 0; i < stems.size (); i++)
144 Grob *stem = stems[i];
145 m = max (m, (Stem::beam_multiplicity (stem).length () + 1));
150 MAKE_SCHEME_CALLBACK (Beam, calc_normal_stems, 1);
152 Beam::calc_normal_stems (SCM smob)
154 Grob *me = unsmob<Grob> (smob);
156 extract_grob_set (me, "stems", stems);
157 SCM val = Grob_array::make_array ();
158 Grob_array *ga = unsmob<Grob_array> (val);
159 for (vsize i = 0; i < stems.size (); i++)
160 if (Stem::is_normal_stem (stems[i]))
166 MAKE_SCHEME_CALLBACK (Beam, calc_direction, 1);
168 Beam::calc_direction (SCM smob)
170 Grob *me = unsmob<Grob> (smob);
172 /* Beams with less than 2 two stems don't make much sense, but could happen
179 Direction dir = CENTER;
181 int count = normal_stem_count (me);
184 extract_grob_set (me, "stems", stems);
185 if (stems.size () == 0)
187 me->warning (_ ("removing beam with no stems"));
190 return SCM_UNSPECIFIED;
194 Grob *stem = first_normal_stem (me);
197 This happens for chord tremolos.
202 if (is_direction (stem->get_property_data ("direction")))
203 dir = to_dir (stem->get_property_data ("direction"));
205 dir = to_dir (stem->get_property ("default-direction"));
207 extract_grob_set (stem, "note-heads", heads);
208 /* default position of Kievan heads with beams is down
209 placing this here avoids warnings downstream */
212 if (scm_is_eq (heads[0]->get_property ("style"),
213 ly_symbol2scm ("kievan")))
225 dir = get_default_dir (me);
227 consider_auto_knees (me);
232 set_stem_directions (me, dir);
235 return scm_from_int (dir);
238 /* We want a maximal number of shared beams, but if there is choice, we
239 * take the one that is closest to the end of the stem. This is for
251 position_with_maximal_common_beams (SCM left_beaming, SCM right_beaming,
255 Slice lslice = int_list_to_slice (scm_cdr (left_beaming));
259 for (int i = lslice[-left_dir];
260 (i - lslice[left_dir]) * left_dir <= 0; i += left_dir)
263 for (SCM s = scm_car (right_beaming); scm_is_pair (s); s = scm_cdr (s))
265 int k = -right_dir * scm_to_int (scm_car (s)) + i;
266 if (scm_is_true (scm_c_memq (scm_from_int (k), left_beaming)))
270 if (count >= best_count)
280 MAKE_SCHEME_CALLBACK (Beam, calc_beaming, 1)
282 Beam::calc_beaming (SCM smob)
284 Grob *me = unsmob<Grob> (smob);
286 extract_grob_set (me, "stems", stems);
289 last_int.set_empty ();
291 SCM last_beaming = scm_cons (SCM_EOL, scm_list_1 (scm_from_int (0)));
292 Direction last_dir = CENTER;
293 for (vsize i = 0; i < stems.size (); i++)
295 Grob *this_stem = stems[i];
296 SCM this_beaming = this_stem->get_property ("beaming");
298 Direction this_dir = get_grob_direction (this_stem);
299 if (scm_is_pair (last_beaming) && scm_is_pair (this_beaming))
301 int start_point = position_with_maximal_common_beams
302 (last_beaming, this_beaming,
303 last_dir ? last_dir : this_dir,
307 for (LEFT_and_RIGHT (d))
309 new_slice.set_empty ();
310 SCM s = index_get_cell (this_beaming, d);
311 for (; scm_is_pair (s); s = scm_cdr (s))
314 = start_point - this_dir * scm_to_int (scm_car (s));
316 new_slice.add_point (new_beam_pos);
317 scm_set_car_x (s, scm_from_int (new_beam_pos));
321 if (!new_slice.is_empty ())
322 last_int = new_slice;
327 FIXME: what's this for?
329 SCM s = scm_cdr (this_beaming);
330 for (; scm_is_pair (s); s = scm_cdr (s))
332 int np = -this_dir * scm_to_int (scm_car (s));
333 scm_set_car_x (s, scm_from_int (np));
334 last_int.add_point (np);
338 if (scm_ilength (scm_cdr (this_beaming)) > 0)
340 last_beaming = this_beaming;
349 operator <(Beam_stem_segment const &a,
350 Beam_stem_segment const &b)
352 return a.rank_ < b.rank_;
355 typedef map<int, vector<Beam_stem_segment> > Position_stem_segments_map;
357 MAKE_SCHEME_CALLBACK (Beam, calc_beam_segments, 1);
359 Beam::calc_beam_segments (SCM smob)
361 /* ugh, this has a side-effect that we need to ensure that
362 Stem #'beaming is correct */
363 Grob *me_grob = unsmob<Grob> (smob);
364 (void) me_grob->get_property ("beaming");
366 Spanner *me = dynamic_cast<Spanner *> (me_grob);
368 extract_grob_set (me, "stems", stems);
370 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
371 for (LEFT_and_RIGHT (d))
372 commonx = me->get_bound (d)->common_refpoint (commonx, X_AXIS);
374 int gap_count = robust_scm2int (me->get_property ("gap-count"), 0);
375 Real gap_length = robust_scm2double (me->get_property ("gap"), 0.0);
377 Position_stem_segments_map stem_segments;
378 Real lt = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
380 /* There are two concepts of "rank" that are used in the following code.
381 The beam_rank is the vertical position of the beam (larger numbers are
382 closer to the noteheads). Beam_stem_segment.rank_, on the other hand,
383 is the horizontal position of the segment (this is incremented by two
384 for each stem; the beam segment on the right side of the stem has
385 a higher rank (by one) than its neighbour to the left). */
387 for (vsize i = 0; i < stems.size (); i++)
389 Grob *stem = stems[i];
390 Real stem_width = robust_scm2double (stem->get_property ("thickness"), 1.0) * lt;
391 Real stem_x = stem->relative_coordinate (commonx, X_AXIS);
392 SCM beaming = stem->get_property ("beaming");
394 for (LEFT_and_RIGHT (d))
396 // Find the maximum and minimum beam ranks.
397 // Given that RANKS is never reset to empty, the interval will always be
398 // smallest for the left beamlet of the first stem, and then it might grow.
399 // Do we really want this? (It only affects the tremolo gaps) --jneem
400 for (SCM s = index_get_cell (beaming, d);
401 scm_is_pair (s); s = scm_cdr (s))
403 if (!scm_is_integer (scm_car (s)))
406 int beam_rank = scm_to_int (scm_car (s));
407 ranks.add_point (beam_rank);
410 for (SCM s = index_get_cell (beaming, d);
411 scm_is_pair (s); s = scm_cdr (s))
413 if (!scm_is_integer (scm_car (s)))
416 int beam_rank = scm_to_int (scm_car (s));
417 Beam_stem_segment seg;
419 seg.stem_x_ = stem_x;
420 seg.rank_ = 2 * i + (d + 1) / 2;
421 seg.width_ = stem_width;
424 seg.max_connect_ = robust_scm2int (stem->get_property ("max-beam-connect"), 1000);
426 Direction stem_dir = get_grob_direction (stem);
429 = (stem_dir * beam_rank < (stem_dir * ranks[-stem_dir] + gap_count));
430 stem_segments[beam_rank].push_back (seg);
435 Drul_array<Real> break_overshoot
436 = robust_scm2drul (me->get_property ("break-overshoot"),
437 Drul_array<Real> (-0.5, 0.0));
439 vector<Beam_segment> segments;
440 for (Position_stem_segments_map::const_iterator i (stem_segments.begin ());
441 i != stem_segments.end (); i++)
443 vector<Beam_stem_segment> segs = (*i).second;
444 vector_sort (segs, less<Beam_stem_segment> ());
446 Beam_segment current;
448 // Iterate over all of the segments of the current beam rank,
449 // merging the adjacent Beam_stem_segments into one Beam_segment
451 int vertical_count = (*i).first;
452 for (vsize j = 0; j < segs.size (); j++)
454 // Keeping track of the different directions here is a little tricky.
455 // segs[j].dir_ is the direction of the beam segment relative to the stem
456 // (ie. segs[j].dir_ == LEFT if the beam segment sticks out to the left of
457 // its stem) whereas event_dir refers to the edge of the beam segment that
458 // we are currently looking at (ie. if segs[j].dir_ == event_dir then we
459 // are looking at that edge of the beam segment that is furthest from its
461 Beam_stem_segment const &seg = segs[j];
462 for (LEFT_and_RIGHT (event_dir))
464 Beam_stem_segment const &neighbor_seg = segs[j + event_dir];
465 // TODO: make names clearer? --jneem
466 // on_line_bound: whether the current segment is on the boundary of the WHOLE beam
467 // on_beam_bound: whether the current segment is on the boundary of just that part
468 // of the beam with the current beam_rank
469 bool on_line_bound = (seg.dir_ == LEFT) ? seg.stem_index_ == 0
470 : seg.stem_index_ == stems.size () - 1;
471 bool on_beam_bound = (event_dir == LEFT) ? j == 0
472 : j == segs.size () - 1;
473 bool inside_stem = (event_dir == LEFT)
474 ? seg.stem_index_ > 0
475 : seg.stem_index_ + 1 < stems.size ();
477 bool event = on_beam_bound
478 || abs (seg.rank_ - neighbor_seg.rank_) > 1
479 || (abs (vertical_count) >= seg.max_connect_
480 || abs (vertical_count) >= neighbor_seg.max_connect_);
483 // Then this edge of the current segment is irrelevant because it will
484 // be connected with the next segment in the event_dir direction.
485 // If we skip the left edge here, the right edge of
486 // the previous segment has already been skipped since
487 // the conditions are symmetric
490 current.vertical_count_ = vertical_count;
491 current.horizontal_[event_dir] = seg.stem_x_;
492 if (seg.dir_ == event_dir)
493 // then we are examining the edge of a beam segment that is furthest
497 && me->get_bound (event_dir)->break_status_dir ())
499 current.horizontal_[event_dir]
500 = (Axis_group_interface::generic_bound_extent (me->get_bound (event_dir),
501 commonx, X_AXIS)[RIGHT]
502 + event_dir * break_overshoot[event_dir]);
506 Grob *stem = stems[seg.stem_index_];
507 Drul_array<Real> beamlet_length
508 = robust_scm2interval (stem->get_property ("beamlet-default-length"), Interval (1.1, 1.1));
509 Drul_array<Real> max_proportion
510 = robust_scm2interval (stem->get_property ("beamlet-max-length-proportion"), Interval (0.75, 0.75));
511 Real length = beamlet_length[seg.dir_];
515 Grob *neighbor_stem = stems[seg.stem_index_ + event_dir];
516 Real neighbor_stem_x = neighbor_stem->relative_coordinate (commonx, X_AXIS);
518 length = min (length,
519 fabs (neighbor_stem_x - seg.stem_x_) * max_proportion[seg.dir_]);
521 current.horizontal_[event_dir] += event_dir * length;
525 // we are examining the edge of a beam segment that is closest
526 // (ie. touching, unless there is a gap) its stem.
528 current.horizontal_[event_dir] += event_dir * seg.width_ / 2;
531 current.horizontal_[event_dir] -= event_dir * gap_length;
533 if (Stem::is_invisible (seg.stem_))
536 Need to do this in case of whole notes. We don't want the
537 heads to collide with the beams.
539 extract_grob_set (seg.stem_, "note-heads", heads);
541 for (vsize k = 0; k < heads.size (); k++)
542 current.horizontal_[event_dir]
543 = event_dir * min (event_dir * current.horizontal_[event_dir],
546 * heads[k]->extent (commonx,
547 X_AXIS)[-event_dir]);
552 if (event_dir == RIGHT)
554 segments.push_back (current);
555 current = Beam_segment ();
562 SCM segments_scm = SCM_EOL;
564 for (vsize i = segments.size (); i--;)
566 segments_scm = scm_cons (scm_list_2 (scm_cons (ly_symbol2scm ("vertical-count"),
567 scm_from_int (segments[i].vertical_count_)),
568 scm_cons (ly_symbol2scm ("horizontal"),
569 ly_interval2scm (segments[i].horizontal_))),
576 MAKE_SCHEME_CALLBACK (Beam, calc_x_positions, 1);
578 Beam::calc_x_positions (SCM smob)
580 Spanner *me = unsmob<Spanner> (smob);
581 SCM segments = me->get_property ("beam-segments");
582 Interval x_positions;
583 x_positions.set_empty ();
584 for (SCM s = segments; scm_is_pair (s); s = scm_cdr (s))
585 x_positions.unite (robust_scm2interval (ly_assoc_get (ly_symbol2scm ("horizontal"),
588 Interval (0.0, 0.0)));
590 // Case for beams without segments (i.e. uniting two skips with a beam)
591 // TODO: should issue a warning? warning likely issued downstream, but couldn't hurt...
592 if (x_positions.is_empty ())
594 extract_grob_set (me, "stems", stems);
595 Grob *common_x = common_refpoint_of_array (stems, me, X_AXIS);
596 for (LEFT_and_RIGHT (d))
597 x_positions[d] = me->relative_coordinate (common_x, X_AXIS);
599 return ly_interval2scm (x_positions);
603 Beam::get_beam_segments (Grob *me)
605 SCM segments_scm = me->get_property ("beam-segments");
606 vector<Beam_segment> segments;
607 for (SCM s = segments_scm; scm_is_pair (s); s = scm_cdr (s))
609 segments.push_back (Beam_segment ());
610 segments.back ().vertical_count_ = robust_scm2int (ly_assoc_get (ly_symbol2scm ("vertical-count"), scm_car (s), SCM_EOL), 0);
611 segments.back ().horizontal_ = robust_scm2interval (ly_assoc_get (ly_symbol2scm ("horizontal"), scm_car (s), SCM_EOL), Interval (0.0, 0.0));
617 MAKE_SCHEME_CALLBACK (Beam, print, 1);
619 Beam::print (SCM grob)
621 Spanner *me = unsmob<Spanner> (grob);
623 TODO - mild code dup for all the commonx calls.
624 Some use just common_refpoint_of_array, some (in print and
625 calc_beam_segments) use this plus calls to get_bound.
627 Figure out if there is any particular reason for this and
628 consolidate in one Beam::get_common function.
630 extract_grob_set (me, "stems", stems);
631 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
632 for (LEFT_and_RIGHT (d))
633 commonx = me->get_bound (d)->common_refpoint (commonx, X_AXIS);
635 vector<Beam_segment> segments = get_beam_segments (me);
637 if (!segments.size ())
640 Real blot = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
642 SCM posns = me->get_property ("quantized-positions");
643 Interval span = robust_scm2interval (me->get_property ("X-positions"), Interval (0, 0));
645 if (!is_number_pair (posns))
647 programming_error ("no beam positions?");
648 pos = Interval (0, 0);
651 pos = ly_scm2realdrul (posns);
653 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
655 Real dy = pos[RIGHT] - pos[LEFT];
656 Real slope = (dy && span.length ()) ? dy / span.length () : 0;
658 Real beam_thickness = get_beam_thickness (me);
659 Real beam_dy = get_beam_translation (me);
661 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
663 Interval placements = robust_scm2interval (me->get_property ("normalized-endpoints"), Interval (0.0, 0.0));
666 vsize extreme = (segments[0].vertical_count_ == 0
667 ? segments[0].vertical_count_
668 : segments.back ().vertical_count_);
670 for (vsize i = 0; i < segments.size (); i++)
672 Real local_slope = slope;
674 Makes local slope proportional to the ratio of the length of this beam
678 local_slope += (feather_dir * segments[i].vertical_count_
680 * placements.length ()
683 Stencil b = Lookup::beam (local_slope, segments[i].horizontal_.length (), beam_thickness, blot);
685 b.translate_axis (segments[i].horizontal_[LEFT], X_AXIS);
686 Real multiplier = feather_dir ? placements[LEFT] : 1.0;
688 Interval weights (1 - multiplier, multiplier);
690 if (feather_dir != LEFT)
693 // we need two translations: the normal one and
694 // the one of the lowest segment
695 size_t idx[] = {i, extreme};
696 Real translations[2];
698 for (int j = 0; j < 2; j++)
699 translations[j] = slope
700 * (segments[idx[j]].horizontal_[LEFT] - span.linear_combination (CENTER))
701 + pos.linear_combination (CENTER)
702 + beam_dy * segments[idx[j]].vertical_count_;
704 Real weighted_average = translations[0] * weights[LEFT] + translations[1] * weights[RIGHT];
707 Tricky. The manipulation of the variable `weighted_average' below ensures
708 that beams with a RIGHT grow direction will start from the position of the
709 lowest segment at 0, and this error will decrease and decrease over the
710 course of the beam. Something with a LEFT grow direction, on the other
711 hand, will always start in the correct place but progressively accrue
712 error at broken places. This code shifts beams up given where they are
713 in the total span length (controlled by the variable `multiplier'). To
714 better understand what it does, try commenting it out: you'll see that
715 all of the RIGHT growing beams immediately start too low and get better
716 over line breaks, whereas all of the LEFT growing beams start just right
717 and get worse over line breaks.
719 Real factor = Interval (multiplier, 1 - multiplier).linear_combination (feather_dir);
721 if (segments[0].vertical_count_ < 0 && feather_dir)
722 weighted_average += beam_dy * (segments.size () - 1) * factor;
724 b.translate_axis (weighted_average, Y_AXIS);
726 the_beam.add_stencil (b);
730 #if (DEBUG_BEAM_SCORING)
731 SCM annotation = me->get_property ("annotation");
732 if (scm_is_string (annotation))
734 extract_grob_set (me, "stems", stems);
737 This code prints the demerits for each beam. Perhaps this
738 should be switchable for those who want to twiddle with the
742 SCM properties = Font_interface::text_font_alist_chain (me);
744 properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-5), SCM_EOL),
747 Direction stem_dir = stems.size () ? to_dir (stems[0]->get_property ("direction")) : UP;
749 Stencil score = *unsmob<Stencil> (Text_interface::interpret_markup
750 (me->layout ()->self_scm (), properties, annotation));
752 if (!score.is_empty ())
754 score.translate_axis (me->relative_coordinate (commonx, X_AXIS), X_AXIS);
755 the_beam.add_at_edge (Y_AXIS, stem_dir, score, 1.0);
760 the_beam.translate_axis (-me->relative_coordinate (commonx, X_AXIS), X_AXIS);
761 return the_beam.smobbed_copy ();
765 Beam::get_default_dir (Grob *me)
767 extract_grob_set (me, "stems", stems);
769 Drul_array<Real> extremes (0.0, 0.0);
770 for (iterof (s, stems); s != stems.end (); s++)
772 Interval positions = Stem::head_positions (*s);
773 for (DOWN_and_UP (d))
775 if (sign (positions[d]) == d)
776 extremes[d] = d * max (d * positions[d], d * extremes[d]);
780 Drul_array<int> total (0, 0);
781 Drul_array<int> count (0, 0);
783 bool force_dir = false;
784 for (vsize i = 0; i < stems.size (); i++)
787 Direction stem_dir = CENTER;
788 SCM stem_dir_scm = s->get_property_data ("direction");
789 if (is_direction (stem_dir_scm))
791 stem_dir = to_dir (stem_dir_scm);
795 stem_dir = to_dir (s->get_property ("default-direction"));
798 stem_dir = to_dir (s->get_property ("neutral-direction"));
803 total[stem_dir] += max (int (- stem_dir * Stem::head_positions (s) [-stem_dir]), 0);
809 if (abs (extremes[UP]) > -extremes[DOWN])
811 else if (extremes[UP] < -extremes[DOWN])
815 Direction dir = CENTER;
816 Direction d = CENTER;
817 if ((d = (Direction) sign (count[UP] - count[DOWN])))
821 && (d = (Direction) sign (total[UP] / count[UP] - total[DOWN] / count[DOWN])))
823 else if ((d = (Direction) sign (total[UP] - total[DOWN])))
826 dir = to_dir (me->get_property ("neutral-direction"));
831 /* Set all stems with non-forced direction to beam direction.
832 Urg: non-forced should become `without/with unforced' direction,
833 once stem gets cleaned-up. */
835 Beam::set_stem_directions (Grob *me, Direction d)
837 extract_grob_set (me, "stems", stems);
839 for (vsize i = 0; i < stems.size (); i++)
843 SCM forcedir = s->get_property_data ("direction");
844 if (!to_dir (forcedir))
845 set_grob_direction (s, d);
850 Only try horizontal beams for knees. No reliable detection of
851 anything else is possible here, since we don't know funky-beaming
852 settings, or X-distances (slopes!) People that want sloped
853 knee-beams, should set the directions manually.
858 this routine should take into account the stemlength scoring
859 of a possible knee/nonknee beam.
862 Beam::consider_auto_knees (Grob *me)
864 SCM scm = me->get_property ("auto-knee-gap");
865 if (!scm_is_number (scm))
868 vector<Interval> forbidden_intervals;
870 extract_grob_set (me, "normal-stems", stems);
872 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
873 Real staff_space = Staff_symbol_referencer::staff_space (me);
875 vector<Interval> head_extents_array;
876 for (vsize i = 0; i < stems.size (); i++)
878 Grob *stem = stems[i];
880 Interval head_extents;
881 if (Stem::head_count (stem))
883 head_extents = Stem::head_positions (stem);
884 head_extents.widen (1);
885 head_extents *= staff_space * 0.5;
888 We could subtract beam Y position, but this routine only
889 sets stem directions, a constant shift does not have an
892 head_extents += stem->pure_relative_y_coordinate (common, 0, INT_MAX);
894 if (to_dir (stem->get_property_data ("direction")))
896 Direction stemdir = to_dir (stem->get_property ("direction"));
897 head_extents[-stemdir] = -stemdir * infinity_f;
900 head_extents_array.push_back (head_extents);
902 forbidden_intervals.push_back (head_extents);
906 Real max_gap_len = 0.0;
908 vector<Interval> allowed_regions
909 = Interval_set::interval_union (forbidden_intervals).complement ().intervals ();
910 for (vsize i = allowed_regions.size () - 1; i != VPOS; i--)
912 Interval gap = allowed_regions[i];
915 the outer gaps are not knees.
917 if (isinf (gap[LEFT]) || isinf (gap[RIGHT]))
920 if (gap.length () >= max_gap_len)
922 max_gap_len = gap.length ();
927 Real beam_translation = get_beam_translation (me);
928 Real beam_thickness = Beam::get_beam_thickness (me);
929 int beam_count = Beam::get_beam_count (me);
930 Real height_of_beams = beam_thickness / 2
931 + (beam_count - 1) * beam_translation;
932 Real threshold = scm_to_double (scm) + height_of_beams;
934 if (max_gap_len > threshold)
937 for (vsize i = 0; i < stems.size (); i++)
939 Grob *stem = stems[i];
940 Interval head_extents = head_extents_array[j++];
942 Direction d = (head_extents.center () < max_gap.center ())
945 stem->set_property ("direction", scm_from_int (d));
947 head_extents.intersect (max_gap);
948 assert (head_extents.is_empty () || head_extents.length () < 1e-6);
953 MAKE_SCHEME_CALLBACK (Beam, calc_stem_shorten, 1)
955 Beam::calc_stem_shorten (SCM smob)
957 Grob *me = unsmob<Grob> (smob);
960 shortening looks silly for x staff beams
963 return scm_from_int (0);
965 Real forced_fraction = 1.0 * forced_stem_count (me)
966 / normal_stem_count (me);
968 int beam_count = get_beam_count (me);
970 SCM shorten_list = me->get_property ("beamed-stem-shorten");
971 if (scm_is_null (shorten_list))
972 return scm_from_int (0);
974 Real staff_space = Staff_symbol_referencer::staff_space (me);
977 = robust_list_ref (beam_count - 1, shorten_list);
978 Real shorten = scm_to_double (shorten_elt) * staff_space;
980 shorten *= forced_fraction;
983 return scm_from_double (shorten);
985 return scm_from_double (0.0);
988 MAKE_SCHEME_CALLBACK (Beam, quanting, 3);
990 Beam::quanting (SCM smob, SCM ys_scm, SCM align_broken_intos)
992 Grob *me = unsmob<Grob> (smob);
993 Drul_array<Real> ys = robust_scm2drul (ys_scm, Drul_array<Real> (infinity_f, -infinity_f));
994 bool cbs = to_boolean (align_broken_intos);
996 Beam_scoring_problem problem (me, ys, cbs);
997 ys = problem.solve ();
999 return ly_interval2scm (ys);
1003 Report slice containing the numbers that are both in (car BEAMING)
1007 where_are_the_whole_beams (SCM beaming)
1011 for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
1013 if (scm_is_true (scm_c_memq (scm_car (s), scm_cdr (beaming))))
1015 l.add_point (scm_to_int (scm_car (s)));
1021 /* Return the Y position of the stem-end, given the Y-left, Y-right
1022 in POS for stem S. This Y position is relative to S. */
1024 Beam::calc_stem_y (Grob *me, Grob *stem, Grob **common,
1025 Real xl, Real xr, Direction feather_dir,
1026 Drul_array<Real> pos, bool french)
1028 Real beam_translation = get_beam_translation (me);
1029 Direction stem_dir = get_grob_direction (stem);
1032 Real relx = dx ? (stem->relative_coordinate (common[X_AXIS], X_AXIS) - xl) / dx : 0;
1033 Real xdir = 2 * relx - 1;
1035 Real stem_y = linear_combination (pos, xdir);
1037 SCM beaming = stem->get_property ("beaming");
1039 Slice beam_slice (french
1040 ? where_are_the_whole_beams (beaming)
1041 : Stem::beam_multiplicity (stem));
1042 if (beam_slice.is_empty ())
1043 beam_slice = Slice (0, 0);
1044 Interval beam_multiplicity (beam_slice[LEFT],
1048 feather dir = 1 , relx 0->1 : factor 0 -> 1
1049 feather dir = 0 , relx 0->1 : factor 1 -> 1
1050 feather dir = -1, relx 0->1 : factor 1 -> 0
1052 Real feather_factor = 1;
1053 if (feather_dir > 0)
1054 feather_factor = relx;
1055 else if (feather_dir < 0)
1056 feather_factor = 1 - relx;
1058 stem_y += feather_factor * beam_translation
1059 * beam_multiplicity[Direction (((french) ? DOWN : UP) * stem_dir)];
1060 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1061 - stem->relative_coordinate (common[Y_AXIS], Y_AXIS);
1067 Hmm. At this time, beam position and slope are determined. Maybe,
1068 stem directions and length should set to relative to the chord's
1069 position of the beam. */
1070 MAKE_SCHEME_CALLBACK (Beam, set_stem_lengths, 1);
1072 Beam::set_stem_lengths (SCM smob)
1074 Grob *me = unsmob<Grob> (smob);
1076 /* trigger callbacks. */
1077 (void) me->get_property ("direction");
1078 (void) me->get_property ("beaming");
1080 SCM posns = me->get_property ("positions");
1082 extract_grob_set (me, "stems", stems);
1087 for (int a = 2; a--;)
1088 common[a] = common_refpoint_of_array (stems, me, Axis (a));
1090 Drul_array<Real> pos = ly_scm2realdrul (posns);
1091 Real staff_space = Staff_symbol_referencer::staff_space (me);
1092 scale_drul (&pos, staff_space);
1096 if (robust_scm2int (me->get_property ("gap-count"), 0))
1099 thick = get_beam_thickness (me);
1102 Grob *fvs = first_normal_stem (me);
1103 Grob *lvs = last_normal_stem (me);
1105 Interval x_span = robust_scm2interval (me->get_property ("X-positions"), Interval (0, 0));
1106 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
1108 for (vsize i = 0; i < stems.size (); i++)
1112 bool french = to_boolean (s->get_property ("french-beaming"));
1113 Real stem_y = calc_stem_y (me, s, common,
1114 x_span[LEFT], x_span[RIGHT], feather_dir,
1115 pos, french && s != lvs && s != fvs);
1118 Make the stems go up to the end of the beam. This doesn't matter
1119 for normal beams, but for tremolo beams it looks silly otherwise.
1122 && !Stem::is_invisible (s))
1123 stem_y += thick * 0.5 * get_grob_direction (s);
1126 Do set_stem_positions for invisible stems too, so tuplet brackets
1127 have a reference point for sloping
1129 Stem::set_stem_positions (s, 2 * stem_y / staff_space);
1136 Beam::set_beaming (Grob *me, Beaming_pattern const *beaming)
1138 extract_grob_set (me, "stems", stems);
1140 for (vsize i = 0; i < stems.size (); i++)
1143 Don't overwrite user settings.
1145 for (LEFT_and_RIGHT (d))
1147 Grob *stem = stems[i];
1148 SCM beaming_prop = stem->get_property ("beaming");
1149 if (scm_is_null (beaming_prop)
1150 || scm_is_null (index_get_cell (beaming_prop, d)))
1152 int count = beaming->beamlet_count (i, d);
1154 && i + 1 < stems.size ()
1155 && Stem::is_invisible (stem))
1156 count = min (count, beaming->beamlet_count (i, -d));
1158 if ( ((i == 0 && d == LEFT)
1159 || (i == stems.size () - 1 && d == RIGHT))
1160 && stems.size () > 1
1161 && to_boolean (me->get_property ("clip-edges")))
1164 Stem::set_beaming (stem, count, d);
1171 Beam::forced_stem_count (Grob *me)
1173 extract_grob_set (me, "normal-stems", stems);
1176 for (vsize i = 0; i < stems.size (); i++)
1180 /* I can imagine counting those boundaries as a half forced stem,
1181 but let's count them full for now. */
1182 Direction defdir = to_dir (s->get_property ("default-direction"));
1184 if (abs (Stem::chord_start_y (s)) > 0.1
1186 && get_grob_direction (s) != defdir)
1193 Beam::normal_stem_count (Grob *me)
1195 extract_grob_set (me, "normal-stems", stems);
1196 return stems.size ();
1200 Beam::first_normal_stem (Grob *me)
1202 extract_grob_set (me, "normal-stems", stems);
1203 return stems.size () ? stems[0] : 0;
1207 Beam::last_normal_stem (Grob *me)
1209 extract_grob_set (me, "normal-stems", stems);
1210 return stems.size () ? stems.back () : 0;
1216 handle rest under beam (do_post: beams are calculated now)
1217 what about combination of collisions and rest under beam.
1221 rest -> stem -> beam -> interpolate_y_position ()
1223 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Beam, rest_collision_callback, 2, 1, "");
1225 Beam::rest_collision_callback (SCM smob, SCM prev_offset)
1227 Grob *rest = unsmob<Grob> (smob);
1228 if (scm_is_number (rest->get_property ("staff-position")))
1229 return scm_from_int (0);
1231 Real offset = robust_scm2double (prev_offset, 0.0);
1233 Grob *st = unsmob<Grob> (rest->get_object ("stem"));
1236 return scm_from_double (0.0);
1237 Grob *beam = unsmob<Grob> (stem->get_object ("beam"));
1239 || !has_interface<Beam> (beam)
1240 || !Beam::normal_stem_count (beam))
1241 return scm_from_double (0.0);
1243 Grob *common_y = rest->common_refpoint (beam, Y_AXIS);
1245 Drul_array<Real> pos (robust_scm2drul (beam->get_property ("positions"),
1246 Drul_array<Real> (0, 0)));
1248 for (LEFT_and_RIGHT (dir))
1249 pos[dir] += beam->relative_coordinate (common_y, Y_AXIS);
1251 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1253 scale_drul (&pos, staff_space);
1255 Real dy = pos[RIGHT] - pos[LEFT];
1257 extract_grob_set (beam, "stems", stems);
1258 Grob *common = common_refpoint_of_array (stems, beam, X_AXIS);
1260 Interval x_span = robust_scm2interval (beam->get_property ("X-positions"),
1261 Interval (0.0, 0.0));
1262 Real x0 = x_span[LEFT];
1263 Real dx = x_span.length ();
1264 Real slope = dy && dx ? dy / dx : 0;
1266 Direction d = get_grob_direction (stem);
1267 Real stem_y = pos[LEFT]
1268 + (stem->relative_coordinate (common, X_AXIS) - x0) * slope;
1270 Real beam_translation = get_beam_translation (beam);
1271 Real beam_thickness = Beam::get_beam_thickness (beam);
1274 TODO: this is not strictly correct for 16th knee beams.
1277 = Stem::beam_multiplicity (stem).length () + 1;
1279 Real height_of_my_beams = beam_thickness / 2
1280 + (beam_count - 1) * beam_translation;
1281 Real beam_y = stem_y - d * height_of_my_beams;
1283 Interval rest_extent = rest->extent (rest, Y_AXIS);
1284 rest_extent.translate (offset + rest->get_parent (Y_AXIS)->relative_coordinate (common_y, Y_AXIS));
1286 Real rest_dim = rest_extent[d];
1287 Real minimum_distance
1288 = staff_space * (robust_scm2double (stem->get_property ("stemlet-length"), 0.0)
1289 + robust_scm2double (rest->get_property ("minimum-distance"), 0.0));
1291 Real shift = d * min (d * (beam_y - d * minimum_distance - rest_dim), 0.0);
1293 shift /= staff_space;
1295 /* Always move discretely by half spaces */
1296 shift = ceil (fabs (shift * 2.0)) / 2.0 * sign (shift);
1298 Interval staff_span = Staff_symbol_referencer::staff_span (rest);
1299 staff_span *= staff_space / 2;
1301 /* Inside staff, move by whole spaces*/
1302 if (staff_span.contains (rest_extent[d] + staff_space * shift)
1303 || staff_span.contains (rest_extent[-d] + staff_space * shift))
1304 shift = ceil (fabs (shift)) * sign (shift);
1306 return scm_from_double (offset + staff_space * shift);
1310 Estimate the position of a rest under a beam,
1311 using the average position of its neighboring heads.
1313 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Beam, pure_rest_collision_callback, 4, 1, "");
1315 Beam::pure_rest_collision_callback (SCM smob,
1320 Real previous = robust_scm2double (prev_offset, 0.0);
1322 Grob *me = unsmob<Grob> (smob);
1323 Grob *stem = unsmob<Grob> (me->get_object ("stem"));
1325 return scm_from_double (previous);
1326 Grob *beam = unsmob<Grob> (stem->get_object ("beam"));
1328 || !Beam::normal_stem_count (beam)
1329 || !is_direction (beam->get_property_data ("direction")))
1330 return scm_from_double (previous);
1332 Real ss = Staff_symbol_referencer::staff_space (me);
1334 extract_grob_set (beam, "stems", stems);
1335 vector<Grob *> my_stems;
1337 for (vsize i = 0; i < stems.size (); i++)
1338 if (Stem::head_count (stems[i]) || stems[i] == stem)
1339 my_stems.push_back (stems[i]);
1343 for (vsize i = 0; i < my_stems.size (); i++)
1344 if (my_stems[i] == stem)
1352 if (idx == (vsize) - 1 || my_stems.size () == 1)
1353 return scm_from_double (previous);
1355 left = right = my_stems[1];
1356 else if (idx == my_stems.size () - 1)
1357 left = right = my_stems[idx - 1];
1360 left = my_stems[idx - 1];
1361 right = my_stems[idx + 1];
1364 /* Estimate the closest beam to be four positions away from the heads, */
1365 Direction beamdir = get_grob_direction (beam);
1366 Real beam_pos = (Stem::head_positions (left)[beamdir]
1367 + Stem::head_positions (right)[beamdir]) / 2.0
1368 + 4.0 * beamdir; // four staff-positions
1369 /* and that the closest beam never crosses staff center by more than two positions */
1370 beam_pos = max (-2.0, beam_pos * beamdir) * beamdir;
1372 Real minimum_distance
1373 = ss * (robust_scm2double (stem->get_property ("stemlet-length"), 0.0)
1374 + robust_scm2double (me->get_property ("minimum-distance"), 0.0));
1375 Real offset = beam_pos * ss / 2.0
1376 - minimum_distance * beamdir
1377 - me->extent (me, Y_AXIS)[beamdir];
1379 /* Always move by a whole number of staff spaces, always away from the beam */
1380 offset = floor (min (0.0, (offset - previous) / ss * beamdir))
1381 * ss * beamdir + previous;
1383 return scm_from_double (offset);
1387 Beam::is_knee (Grob *me)
1389 SCM k = me->get_property ("knee");
1390 if (scm_is_bool (k))
1391 return ly_scm2bool (k);
1395 extract_grob_set (me, "stems", stems);
1396 for (vsize i = stems.size (); i--;)
1398 Direction dir = get_grob_direction (stems[i]);
1407 me->set_property ("knee", ly_bool2scm (knee));
1413 Beam::is_cross_staff (Grob *me)
1415 extract_grob_set (me, "stems", stems);
1416 Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
1417 for (vsize i = 0; i < stems.size (); i++)
1418 if (Staff_symbol_referencer::get_staff_symbol (stems[i]) != staff_symbol)
1423 MAKE_SCHEME_CALLBACK (Beam, calc_cross_staff, 1)
1425 Beam::calc_cross_staff (SCM smob)
1427 return scm_from_bool (is_cross_staff (unsmob<Grob> (smob)));
1431 Beam::get_direction_beam_count (Grob *me, Direction d)
1433 extract_grob_set (me, "stems", stems);
1436 for (vsize i = stems.size (); i--;)
1439 Should we take invisible stems into account?
1441 if (get_grob_direction (stems[i]) == d)
1442 bc = max (bc, (Stem::beam_multiplicity (stems[i]).length () + 1));
1448 ADD_INTERFACE (Beam,
1451 "The @code{beam-thickness} property is the weight of beams,"
1452 " measured in staffspace. The @code{direction} property is"
1453 " not user-serviceable. Use the @code{direction} property"
1454 " of @code{Stem} instead.\n"
1455 "The following properties may be set in the @code{details}"
1459 "@item stem-length-demerit-factor\n"
1460 "Demerit factor used for inappropriate stem lengths.\n"
1461 "@item secondary-beam-demerit\n"
1462 "Demerit used in quanting calculations for multiple"
1464 "@item region-size\n"
1465 "Size of region for checking quant scores.\n"
1467 "Epsilon for beam quant code to check for presence"
1469 "@item stem-length-limit-penalty\n"
1470 "Penalty for differences in stem lengths on a beam.\n"
1471 "@item damping-direction-penalty\n"
1472 "Demerit penalty applied when beam direction is different"
1473 " from damping direction.\n"
1474 "@item hint-direction-penalty\n"
1475 "Demerit penalty applied when beam direction is different"
1476 " from damping direction, but damping slope is"
1477 " <= @code{round-to-zero-slope}.\n"
1478 "@item musical-direction-factor\n"
1479 "Demerit scaling factor for difference between"
1480 " beam slope and music slope.\n"
1481 "@item ideal-slope-factor\n"
1482 "Demerit scaling factor for difference between"
1483 " beam slope and damping slope.\n"
1484 "@item round-to-zero-slope\n"
1485 "Damping slope which is considered zero for purposes of"
1486 " calculating direction penalties.\n"
1492 "beamed-stem-shorten "
1499 "collision-interfaces "
1500 "collision-voice-only "
1512 "neutral-direction "
1515 "quantized-positions "