Nitpick: ly:spanner-bound grob name slur -> spanner.
[lilypond.git] / lily / beam.cc
blobe803b806c89fa974eddbf81769325f2dff63795b
1 /*
2 beam.cc -- implement Beam
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2009 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 /* There are two concepts of "rank" that are used in the following code.
345 The beam_rank is the vertical position of the beam (larger numbers are
346 closer to the noteheads). Beam_stem_segment.rank_, on the other hand,
347 is the horizontal position of the segment (this is incremented by two
348 for each stem; the beam segment on the right side of the stem has
349 a higher rank (by one) than its neighbour to the left). */
350 Slice ranks;
351 for (vsize i = 0; i < stems.size (); i++)
353 Grob *stem = stems[i];
354 Real stem_width = robust_scm2double (stem->get_property ("thickness"), 1.0) * lt;
355 Real stem_x = stem->relative_coordinate (commonx, X_AXIS);
356 SCM beaming = stem->get_property ("beaming");
357 Direction d = LEFT;
360 // Find the maximum and minimum beam ranks.
361 // Given that RANKS is never reset to empty, the interval will always be
362 // smallest for the left beamlet of the first stem, and then it might grow.
363 // Do we really want this? (It only affects the tremolo gaps) --jneem
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 ranks.add_point (beam_rank);
374 for (SCM s = index_get_cell (beaming, d);
375 scm_is_pair (s); s = scm_cdr (s))
377 if (!scm_is_integer (scm_car (s)))
378 continue;
380 int beam_rank = scm_to_int (scm_car (s));
381 Beam_stem_segment seg;
382 seg.stem_ = stem;
383 seg.stem_x_ = stem_x;
384 seg.rank_ = 2 * i + (d+1)/2;
385 seg.width_ = stem_width;
386 seg.stem_index_ = i;
387 seg.dir_ = d;
388 seg.max_connect_ = robust_scm2int (stem->get_property ("max-beam-connect"), 1000);
390 Direction stem_dir = get_grob_direction (stem);
392 seg.gapped_
393 = (stem_dir * beam_rank < (stem_dir * ranks[-stem_dir] + gap_count));
394 stem_segments[beam_rank].push_back (seg);
397 while (flip (&d) != LEFT);
400 Drul_array<Real> break_overshoot
401 = robust_scm2drul (me->get_property ("break-overshoot"),
402 Drul_array<Real> (-0.5, 0.0));
404 vector<Beam_segment> segments;
405 for (Position_stem_segments_map::const_iterator i (stem_segments.begin ());
406 i != stem_segments.end (); i++)
408 vector<Beam_stem_segment> segs = (*i).second;
409 vector_sort (segs, less<Beam_stem_segment> ());
411 Beam_segment current;
413 // Iterate over all of the segments of the current beam rank,
414 // merging the adjacent Beam_stem_segments into one Beam_segment
415 // when appropriate.
416 int vertical_count = (*i).first;
417 for (vsize j = 0; j < segs.size (); j++)
419 // Keeping track of the different directions here is a little tricky.
420 // segs[j].dir_ is the direction of the beam segment relative to the stem
421 // (ie. segs[j].dir_ == LEFT if the beam segment sticks out to the left of
422 // its stem) whereas event_dir refers to the edge of the beam segment that
423 // we are currently looking at (ie. if segs[j].dir_ == event_dir then we
424 // are looking at that edge of the beam segment that is furthest from its
425 // stem).
426 Direction event_dir = LEFT;
427 Beam_stem_segment const& seg = segs[j];
430 Beam_stem_segment const& neighbor_seg = segs[j + event_dir];
431 // TODO: make names clearer? --jneem
432 // on_line_bound: whether the current segment is on the boundary of the WHOLE beam
433 // on_beam_bound: whether the current segment is on the boundary of just that part
434 // of the beam with the current beam_rank
435 bool on_line_bound = (seg.dir_ == LEFT) ? seg.stem_index_ == 0
436 : seg.stem_index_ == stems.size() - 1;
437 bool on_beam_bound = (event_dir == LEFT) ? j == 0 :
438 j == segs.size () - 1;
439 bool inside_stem = (event_dir == LEFT)
440 ? seg.stem_index_ > 0
441 : seg.stem_index_ + 1 < stems.size () ;
443 bool event = on_beam_bound
444 || abs (seg.rank_ - neighbor_seg.rank_) > 1
445 || (abs (vertical_count) >= seg.max_connect_
446 || abs (vertical_count) >= neighbor_seg.max_connect_);
448 if (!event)
449 // Then this edge of the current segment is irrelevent because it will
450 // be connected with the next segment in the event_dir direction.
451 continue;
453 current.vertical_count_ = vertical_count;
454 current.horizontal_[event_dir] = seg.stem_x_;
455 if (seg.dir_ == event_dir)
456 // then we are examining the edge of a beam segment that is furthest
457 // from its stem.
459 if (on_line_bound
460 && me->get_bound (event_dir)->break_status_dir ())
462 current.horizontal_[event_dir]
463 = (robust_relative_extent (me->get_bound (event_dir),
464 commonx, X_AXIS)[RIGHT]
465 + event_dir * break_overshoot[event_dir]);
467 else
469 Grob *stem = stems[seg.stem_index_];
470 Drul_array<Real> beamlet_length =
471 robust_scm2interval (stem->get_property ("beamlet-default-length"), Interval (1.1, 1.1));
472 Drul_array<Real> max_proportion =
473 robust_scm2interval (stem->get_property ("beamlet-max-length-proportion"), Interval (0.75, 0.75));
474 Real length = beamlet_length[seg.dir_];
476 if (inside_stem)
478 Grob *neighbor_stem = stems[seg.stem_index_ + event_dir];
479 Real neighbor_stem_x = neighbor_stem->relative_coordinate (commonx, X_AXIS);
481 length = min (length,
482 fabs (neighbor_stem_x - seg.stem_x_) * max_proportion[seg.dir_]);
484 current.horizontal_[event_dir] += event_dir * length;
487 else
488 // we are examining the edge of a beam segment that is closest
489 // (ie. touching, unless there is a gap) its stem.
491 current.horizontal_[event_dir] += event_dir * seg.width_/2;
492 if (seg.gapped_)
494 current.horizontal_[event_dir] -= event_dir * gap_length;
496 if (Stem::is_invisible (seg.stem_))
499 Need to do this in case of whole notes. We don't want the
500 heads to collide with the beams.
502 extract_grob_set (seg.stem_, "note-heads", heads);
504 for (vsize k = 0; k < heads.size (); k ++)
505 current.horizontal_[event_dir]
506 = event_dir * min (event_dir * current.horizontal_[event_dir],
507 - gap_length/2
508 + event_dir
509 * heads[k]->extent (commonx,
510 X_AXIS)[-event_dir]);
515 if (event_dir == RIGHT)
517 segments.push_back (current);
518 current = Beam_segment ();
521 while (flip (&event_dir) != LEFT);
526 return segments;
529 MAKE_SCHEME_CALLBACK (Beam, print, 1);
531 Beam::print (SCM grob)
533 Spanner *me = unsmob_spanner (grob);
534 Grob *commonx = 0;
535 vector<Beam_segment> segments = get_beam_segments (me, &commonx);
537 Interval span;
538 if (normal_stem_count (me))
540 span[LEFT] = first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
541 span[RIGHT] = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
543 else
545 extract_grob_set (me, "stems", stems);
546 span[LEFT] = stems[0]->relative_coordinate (commonx, X_AXIS);
547 span[RIGHT] = stems.back ()->relative_coordinate (commonx, X_AXIS);
550 Real blot = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
552 SCM posns = me->get_property ("quantized-positions");
553 Interval pos;
554 if (!is_number_pair (posns))
556 programming_error ("no beam positions?");
557 pos = Interval (0, 0);
559 else
560 pos = ly_scm2realdrul (posns);
562 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
564 Real dy = pos[RIGHT] - pos[LEFT];
565 Real slope = (dy && span.length ()) ? dy / span.length () : 0;
567 Real thick = get_thickness (me);
568 Real beam_dy = get_beam_translation (me);
570 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
572 Stencil the_beam;
573 for (vsize i = 0; i < segments.size (); i ++)
575 Real local_slope = slope;
576 if (feather_dir)
578 local_slope += feather_dir * segments[i].vertical_count_ * beam_dy / span.length ();
581 Stencil b = Lookup::beam (local_slope, segments[i].horizontal_.length (), thick, blot);
583 b.translate_axis (segments[i].horizontal_[LEFT], X_AXIS);
585 b.translate_axis (local_slope
586 * (segments[i].horizontal_[LEFT] - span.linear_combination (feather_dir))
587 + pos.linear_combination (feather_dir)
588 + beam_dy * segments[i].vertical_count_, Y_AXIS);
589 the_beam.add_stencil (b);
592 #if (DEBUG_BEAM_SCORING)
593 SCM annotation = me->get_property ("annotation");
594 if (!scm_is_string (annotation))
596 SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-beam-scoring"));
597 if (to_boolean (debug))
598 annotation = me->get_property ("quant-score");
601 if (scm_is_string (annotation))
603 extract_grob_set (me, "stems", stems);
606 This code prints the demerits for each beam. Perhaps this
607 should be switchable for those who want to twiddle with the
608 parameters.
610 string str;
611 SCM properties = Font_interface::text_font_alist_chain (me);
613 Direction stem_dir = stems.size () ? to_dir (stems[0]->get_property ("direction")) : UP;
615 Stencil score = *unsmob_stencil (Text_interface::interpret_markup
616 (me->layout ()->self_scm (), properties, annotation));
618 if (!score.is_empty ())
620 score.translate_axis (me->relative_coordinate(commonx, X_AXIS), X_AXIS);
621 the_beam.add_at_edge (Y_AXIS, stem_dir, score, 1.0);
624 #endif
626 the_beam.translate_axis (-me->relative_coordinate (commonx, X_AXIS), X_AXIS);
627 return the_beam.smobbed_copy ();
630 Direction
631 Beam::get_default_dir (Grob *me)
633 extract_grob_set (me, "stems", stems);
635 Drul_array<Real> extremes (0.0, 0.0);
636 for (iterof (s, stems); s != stems.end (); s++)
638 Interval positions = Stem::head_positions (*s);
639 Direction d = DOWN;
642 if (sign (positions[d]) == d)
643 extremes[d] = d * max (d * positions[d], d * extremes[d]);
645 while (flip (&d) != DOWN);
648 Drul_array<int> total (0, 0);
649 Drul_array<int> count (0, 0);
651 bool force_dir = false;
652 for (vsize i = 0; i < stems.size (); i++)
654 Grob *s = stems[i];
655 Direction stem_dir = CENTER;
656 SCM stem_dir_scm = s->get_property_data ("direction");
657 if (is_direction (stem_dir_scm))
659 stem_dir = to_dir (stem_dir_scm);
660 force_dir = true;
662 else
663 stem_dir = to_dir (s->get_property ("default-direction"));
665 if (!stem_dir)
666 stem_dir = to_dir (s->get_property ("neutral-direction"));
668 if (stem_dir)
670 count[stem_dir] ++;
671 total[stem_dir] += max (int (- stem_dir * Stem::head_positions (s) [-stem_dir]), 0);
676 if (!force_dir)
678 if (abs (extremes[UP]) > -extremes[DOWN])
679 return DOWN;
680 else if (extremes[UP] < -extremes[DOWN])
681 return UP;
684 Direction dir = CENTER;
685 Direction d = CENTER;
686 if ((d = (Direction) sign (count[UP] - count[DOWN])))
687 dir = d;
688 else if (count[UP]
689 && count[DOWN]
690 && (d = (Direction) sign (total[UP] / count[UP] - total[DOWN]/count[DOWN])))
691 dir = d;
692 else if ((d = (Direction) sign (total[UP] - total[DOWN])))
693 dir = d;
694 else
695 dir = to_dir (me->get_property ("neutral-direction"));
697 return dir;
700 /* Set all stems with non-forced direction to beam direction.
701 Urg: non-forced should become `without/with unforced' direction,
702 once stem gets cleaned-up. */
703 void
704 Beam::set_stem_directions (Grob *me, Direction d)
706 extract_grob_set (me, "stems", stems);
708 for (vsize i = 0; i < stems.size (); i++)
710 Grob *s = stems[i];
712 SCM forcedir = s->get_property_data ("direction");
713 if (!to_dir (forcedir))
714 set_grob_direction (s, d);
719 Only try horizontal beams for knees. No reliable detection of
720 anything else is possible here, since we don't know funky-beaming
721 settings, or X-distances (slopes!) People that want sloped
722 knee-beams, should set the directions manually.
725 TODO:
727 this routine should take into account the stemlength scoring
728 of a possible knee/nonknee beam.
730 void
731 Beam::consider_auto_knees (Grob *me)
733 SCM scm = me->get_property ("auto-knee-gap");
734 if (!scm_is_number (scm))
735 return;
737 Interval_set gaps;
739 gaps.set_full ();
741 extract_grob_set (me, "normal-stems", stems);
743 Grob *common = common_refpoint_of_array (stems, me, Y_AXIS);
744 Real staff_space = Staff_symbol_referencer::staff_space (me);
746 vector<Interval> head_extents_array;
747 for (vsize i = 0; i < stems.size (); i++)
749 Grob *stem = stems[i];
751 Interval head_extents = Stem::head_positions (stem);
752 if (!head_extents.is_empty ())
754 head_extents[LEFT] += -1;
755 head_extents[RIGHT] += 1;
756 head_extents *= staff_space * 0.5;
759 We could subtract beam Y position, but this routine only
760 sets stem directions, a constant shift does not have an
761 influence.
763 head_extents += stem->pure_relative_y_coordinate (common, 0, INT_MAX);
765 if (to_dir (stem->get_property_data ("direction")))
767 Direction stemdir = to_dir (stem->get_property ("direction"));
768 head_extents[-stemdir] = -stemdir * infinity_f;
771 head_extents_array.push_back (head_extents);
773 gaps.remove_interval (head_extents);
776 Interval max_gap;
777 Real max_gap_len = 0.0;
779 for (vsize i = gaps.allowed_regions_.size () -1; i != VPOS ;i--)
781 Interval gap = gaps.allowed_regions_[i];
784 the outer gaps are not knees.
786 if (isinf (gap[LEFT]) || isinf (gap[RIGHT]))
787 continue;
789 if (gap.length () >= max_gap_len)
791 max_gap_len = gap.length ();
792 max_gap = gap;
796 Real beam_translation = get_beam_translation (me);
797 Real beam_thickness = Beam::get_thickness (me);
798 int beam_count = Beam::get_beam_count (me);
799 Real height_of_beams = beam_thickness / 2
800 + (beam_count - 1) * beam_translation;
801 Real threshold = scm_to_double (scm) + height_of_beams;
803 if (max_gap_len > threshold)
805 int j = 0;
806 for (vsize i = 0; i < stems.size (); i++)
808 Grob *stem = stems[i];
809 Interval head_extents = head_extents_array[j++];
811 Direction d = (head_extents.center () < max_gap.center ())
812 ? UP : DOWN;
814 stem->set_property ("direction", scm_from_int (d));
816 head_extents.intersect (max_gap);
817 assert (head_extents.is_empty () || head_extents.length () < 1e-6);
822 /* Set stem's shorten property if unset.
824 TODO:
825 take some y-position (chord/beam/nearest?) into account
826 scmify forced-fraction
828 This is done in beam because the shorten has to be uniform over the
829 entire beam.
834 void
835 set_minimum_dy (Grob *me, Real *dy)
837 if (*dy)
840 If dy is smaller than the smallest quant, we
841 get absurd direction-sign penalties.
844 Real ss = Staff_symbol_referencer::staff_space (me);
845 Real thickness = Beam::get_thickness (me) / ss;
846 Real slt = Staff_symbol_referencer::line_thickness (me) / ss;
847 Real sit = (thickness - slt) / 2;
848 Real inter = 0.5;
849 Real hang = 1.0 - (thickness - slt) / 2;
851 *dy = sign (*dy) * max (fabs (*dy),
852 min (min (sit, inter), hang));
858 MAKE_SCHEME_CALLBACK (Beam, calc_stem_shorten, 1)
860 Beam::calc_stem_shorten (SCM smob)
862 Grob *me = unsmob_grob (smob);
865 shortening looks silly for x staff beams
867 if (is_knee (me))
868 return scm_from_int (0);
870 Real forced_fraction = 1.0 * forced_stem_count (me)
871 / normal_stem_count (me);
873 int beam_count = get_beam_count (me);
875 SCM shorten_list = me->get_property ("beamed-stem-shorten");
876 if (shorten_list == SCM_EOL)
877 return scm_from_int (0);
879 Real staff_space = Staff_symbol_referencer::staff_space (me);
881 SCM shorten_elt
882 = robust_list_ref (beam_count -1, shorten_list);
883 Real shorten = scm_to_double (shorten_elt) * staff_space;
885 shorten *= forced_fraction;
888 if (shorten)
889 return scm_from_double (shorten);
891 return scm_from_double (0.0);
895 Interval
896 Beam::no_visible_stem_positions (Grob *me, Interval default_value)
898 extract_grob_set (me, "stems", stems);
899 if (stems.empty ())
900 return default_value;
902 Interval head_positions;
903 Slice multiplicity;
904 for (vsize i = 0; i < stems.size(); i++)
906 head_positions.unite (Stem::head_positions (stems[i]));
907 multiplicity.unite (Stem::beam_multiplicity (stems[i]));
910 Direction dir = get_grob_direction (me);
911 Real y = head_positions[dir]
912 * 0.5 * Staff_symbol_referencer::staff_space (me)
913 + dir * get_beam_translation (me) * (multiplicity.length () + 1);
915 y /= Staff_symbol_referencer::staff_space (me);
916 return Interval (y,y);
921 Compute a first approximation to the beam slope.
923 MAKE_SCHEME_CALLBACK (Beam, calc_least_squares_positions, 2);
925 Beam::calc_least_squares_positions (SCM smob, SCM /* posns */)
927 Grob *me = unsmob_grob (smob);
929 int count = normal_stem_count (me);
930 Interval pos (0,0);
931 if (count < 1)
932 return ly_interval2scm (no_visible_stem_positions (me, pos));
934 vector<Real> x_posns;
935 extract_grob_set (me, "normal-stems", stems);
936 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
937 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
939 Real my_y = me->relative_coordinate (commony, Y_AXIS);
941 Grob *fvs = first_normal_stem (me);
942 Grob *lvs = last_normal_stem (me);
944 Interval ideal (Stem::get_stem_info (fvs).ideal_y_
945 + fvs->relative_coordinate (commony, Y_AXIS) - my_y,
946 Stem::get_stem_info (lvs).ideal_y_
947 + lvs->relative_coordinate (commony, Y_AXIS) - my_y);
949 Real x0 = first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
950 for (vsize i = 0; i < stems.size (); i++)
952 Grob *s = stems[i];
954 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
955 x_posns.push_back (x);
957 Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS) - x0;
959 Real y = 0;
960 Real slope = 0;
961 Real dy = 0;
962 Real ldy = 0.0;
963 if (!ideal.delta ())
965 Interval chord (Stem::chord_start_y (stems[0]),
966 Stem::chord_start_y (stems.back ()));
968 /* Simple beams (2 stems) on middle line should be allowed to be
969 slightly sloped.
971 However, if both stems reach middle line,
972 ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
974 For that case, we apply artificial slope */
975 if (!ideal[LEFT] && chord.delta () && count == 2)
977 /* FIXME. -> UP */
978 Direction d = (Direction) (sign (chord.delta ()) * UP);
979 pos[d] = get_thickness (me) / 2;
980 pos[-d] = -pos[d];
982 else
983 pos = ideal;
986 For broken beams this doesn't work well. In this case, the
987 slope esp. of the first part of a broken beam should predict
988 where the second part goes.
990 ldy = pos[RIGHT] - pos[LEFT];
992 else
994 vector<Offset> ideals;
995 for (vsize i = 0; i < stems.size (); i++)
997 Grob *s = stems[i];
998 ideals.push_back (Offset (x_posns[i],
999 Stem::get_stem_info (s).ideal_y_
1000 + s->relative_coordinate (commony, Y_AXIS)
1001 - my_y));
1004 minimise_least_squares (&slope, &y, ideals);
1006 dy = slope * dx;
1008 set_minimum_dy (me, &dy);
1010 ldy = dy;
1011 pos = Interval (y, (y + dy));
1015 "position" is relative to the staff.
1017 scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
1019 me->set_property ("least-squares-dy", scm_from_double (ldy));
1020 return ly_interval2scm (pos);
1024 We can't combine with previous function, since check concave and
1025 slope damping comes first.
1027 TODO: we should use the concaveness to control the amount of damping
1028 applied.
1030 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 2);
1032 Beam::shift_region_to_valid (SCM grob, SCM posns)
1034 Grob *me = unsmob_grob (grob);
1036 Code dup.
1038 vector<Real> x_posns;
1039 extract_grob_set (me, "stems", stems);
1040 Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
1041 Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
1043 Grob *fvs = first_normal_stem (me);
1045 if (!fvs)
1046 return posns;
1048 Real x0 = fvs->relative_coordinate (commonx, X_AXIS);
1049 for (vsize i = 0; i < stems.size (); i++)
1051 Grob *s = stems[i];
1053 Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
1054 x_posns.push_back (x);
1057 Grob *lvs = last_normal_stem (me);
1058 if (!lvs)
1059 return posns;
1061 Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
1063 Drul_array<Real> pos = ly_scm2interval (posns);
1065 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
1067 Real dy = pos[RIGHT] - pos[LEFT];
1068 Real y = pos[LEFT];
1069 Real slope = dx ? (dy / dx) : 0.0;
1072 Shift the positions so that we have a chance of finding good
1073 quants (i.e. no short stem failures.)
1075 Interval feasible_left_point;
1076 feasible_left_point.set_full ();
1077 for (vsize i = 0; i < stems.size (); i++)
1079 Grob *s = stems[i];
1080 if (Stem::is_invisible (s))
1081 continue;
1083 Direction d = get_grob_direction (s);
1085 Real left_y
1086 = Stem::get_stem_info (s).shortest_y_
1087 - slope * x_posns [i];
1090 left_y is now relative to the stem S. We want relative to
1091 ourselves, so translate:
1093 left_y
1094 += + s->relative_coordinate (commony, Y_AXIS)
1095 - me->relative_coordinate (commony, Y_AXIS);
1097 Interval flp;
1098 flp.set_full ();
1099 flp[-d] = left_y;
1101 feasible_left_point.intersect (flp);
1104 if (feasible_left_point.is_empty ())
1105 warning (_ ("no viable initial configuration found: may not find good beam slope"));
1106 else if (!feasible_left_point.contains (y))
1108 const int REGION_SIZE = 2; // UGH UGH
1109 if (isinf (feasible_left_point[DOWN]))
1110 y = feasible_left_point[UP] - REGION_SIZE;
1111 else if (isinf (feasible_left_point[UP]))
1112 y = feasible_left_point[DOWN]+ REGION_SIZE;
1113 else
1114 y = feasible_left_point.center ();
1117 pos = Drul_array<Real> (y, (y + dy));
1118 scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
1120 return ly_interval2scm (pos);
1123 /* This neat trick is by Werner Lemberg,
1124 damped = tanh (slope)
1125 corresponds with some tables in [Wanske] CHECKME */
1126 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 2);
1128 Beam::slope_damping (SCM smob, SCM posns)
1130 Grob *me = unsmob_grob (smob);
1131 Drul_array<Real> pos = ly_scm2interval (posns);
1133 if (normal_stem_count (me) <= 1)
1134 return posns;
1137 SCM s = me->get_property ("damping");
1138 Real damping = scm_to_double (s);
1139 Real concaveness = robust_scm2double (me->get_property ("concaveness"), 0.0);
1140 if (concaveness >= 10000)
1142 pos[LEFT] = pos[RIGHT];
1143 me->set_property ("least-squares-dy", scm_from_double (0));
1144 damping = 0;
1147 if (damping)
1149 scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
1151 Real dy = pos[RIGHT] - pos[LEFT];
1153 Grob *fvs = first_normal_stem (me);
1154 Grob *lvs = last_normal_stem (me);
1156 Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1158 Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS)
1159 - first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
1161 Real slope = dy && dx ? dy / dx : 0;
1163 slope = 0.6 * tanh (slope) / (damping + concaveness);
1165 Real damped_dy = slope * dx;
1167 set_minimum_dy (me, &damped_dy);
1169 pos[LEFT] += (dy - damped_dy) / 2;
1170 pos[RIGHT] -= (dy - damped_dy) / 2;
1172 scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
1175 return ly_interval2scm (pos);
1179 Report slice containing the numbers that are both in (car BEAMING)
1180 and (cdr BEAMING)
1182 Slice
1183 where_are_the_whole_beams (SCM beaming)
1185 Slice l;
1187 for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
1189 if (scm_c_memq (scm_car (s), scm_cdr (beaming)) != SCM_BOOL_F)
1191 l.add_point (scm_to_int (scm_car (s)));
1194 return l;
1197 /* Return the Y position of the stem-end, given the Y-left, Y-right
1198 in POS for stem S. This Y position is relative to S. */
1199 Real
1200 Beam::calc_stem_y (Grob *me, Grob *stem, Grob **common,
1201 Real xl, Real xr, Direction feather_dir,
1202 Drul_array<Real> pos, bool french)
1204 Real beam_translation = get_beam_translation (me);
1205 Direction stem_dir = get_grob_direction (stem);
1207 Real dx = xr - xl;
1208 Real relx = dx ? (stem->relative_coordinate (common[X_AXIS], X_AXIS) - xl)/dx : 0;
1209 Real xdir = 2*relx-1;
1211 Real stem_y = linear_combination(pos, xdir);
1213 SCM beaming = stem->get_property ("beaming");
1215 Slice beam_slice (french
1216 ? where_are_the_whole_beams (beaming)
1217 : Stem::beam_multiplicity (stem));
1218 if (beam_slice.is_empty ())
1219 beam_slice = Slice (0,0);
1220 Interval beam_multiplicity(beam_slice[LEFT],
1221 beam_slice[RIGHT]);
1224 feather dir = 1 , relx 0->1 : factor 0 -> 1
1225 feather dir = 0 , relx 0->1 : factor 1 -> 1
1226 feather dir = -1, relx 0->1 : factor 1 -> 0
1228 Real feather_factor = 1;
1229 if (feather_dir > 0)
1230 feather_factor = relx;
1231 else if (feather_dir < 0)
1232 feather_factor = 1 - relx;
1234 stem_y += feather_factor * beam_translation
1235 * beam_multiplicity[Direction(((french) ? DOWN : UP)*stem_dir)];
1236 Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1237 - stem->relative_coordinate (common[Y_AXIS], Y_AXIS);
1239 return stem_y + id;
1243 Hmm. At this time, beam position and slope are determined. Maybe,
1244 stem directions and length should set to relative to the chord's
1245 position of the beam. */
1246 MAKE_SCHEME_CALLBACK (Beam, set_stem_lengths, 1);
1248 Beam::set_stem_lengths (SCM smob)
1250 Grob *me = unsmob_grob (smob);
1252 /* trigger callbacks. */
1253 (void) me->get_property ("direction");
1254 (void) me->get_property ("beaming");
1256 SCM posns = me->get_property ("positions");
1258 extract_grob_set (me, "stems", stems);
1259 if (!stems.size ())
1260 return posns;
1262 Grob *common[2];
1263 for (int a = 2; a--;)
1264 common[a] = common_refpoint_of_array (stems, me, Axis (a));
1266 Drul_array<Real> pos = ly_scm2realdrul (posns);
1267 Real staff_space = Staff_symbol_referencer::staff_space (me);
1268 scale_drul (&pos, staff_space);
1270 bool gap = false;
1271 Real thick = 0.0;
1272 if (robust_scm2int (me->get_property ("gap-count"), 0))
1274 gap = true;
1275 thick = get_thickness (me);
1278 Grob *fvs = first_normal_stem (me);
1279 Grob *lvs = last_normal_stem (me);
1281 Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1282 Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1283 Direction feather_dir = to_dir (me->get_property ("grow-direction"));
1285 for (vsize i = 0; i < stems.size (); i++)
1287 Grob *s = stems[i];
1289 bool french = to_boolean (s->get_property ("french-beaming"));
1290 Real stem_y = calc_stem_y (me, s, common,
1291 xl, xr, feather_dir,
1292 pos, french && s != lvs && s!= fvs);
1295 Make the stems go up to the end of the beam. This doesn't matter
1296 for normal beams, but for tremolo beams it looks silly otherwise.
1298 if (gap
1299 && !Stem::is_invisible (s))
1300 stem_y += thick * 0.5 * get_grob_direction (s);
1303 Do set_stemend for invisible stems too, so tuplet brackets
1304 have a reference point for sloping
1306 Stem::set_stemend (s, 2 * stem_y / staff_space);
1309 return posns;
1312 void
1313 Beam::set_beaming (Grob *me, Beaming_pattern const *beaming)
1315 extract_grob_set (me, "stems", stems);
1317 Direction d = LEFT;
1318 for (vsize i = 0; i < stems.size (); i++)
1321 Don't overwrite user settings.
1325 Grob *stem = stems[i];
1326 SCM beaming_prop = stem->get_property ("beaming");
1327 if (beaming_prop == SCM_EOL
1328 || index_get_cell (beaming_prop, d) == SCM_EOL)
1330 int count = beaming->beamlet_count (i, d);
1331 if (i > 0
1332 && i + 1 < stems.size ()
1333 && Stem::is_invisible (stem))
1334 count = min (count, beaming->beamlet_count (i,-d));
1336 if ( ((i == 0 && d == LEFT)
1337 || (i == stems.size ()-1 && d == RIGHT))
1338 && stems.size () > 1
1339 && to_boolean (me->get_property ("clip-edges")))
1340 count = 0;
1342 Stem::set_beaming (stem, count, d);
1345 while (flip (&d) != LEFT);
1350 Beam::forced_stem_count (Grob *me)
1352 extract_grob_set (me, "normal-stems", stems);
1354 int f = 0;
1355 for (vsize i = 0; i < stems.size (); i++)
1357 Grob *s = stems[i];
1359 /* I can imagine counting those boundaries as a half forced stem,
1360 but let's count them full for now. */
1361 Direction defdir = to_dir (s->get_property ("default-direction"));
1363 if (abs (Stem::chord_start_y (s)) > 0.1
1364 && defdir
1365 && get_grob_direction (s) != defdir)
1366 f++;
1368 return f;
1372 Beam::normal_stem_count (Grob *me)
1374 extract_grob_set (me, "normal-stems", stems);
1375 return stems.size ();
1378 Grob *
1379 Beam::first_normal_stem (Grob *me)
1381 extract_grob_set (me, "normal-stems", stems);
1382 return stems.size () ? stems[0] : 0;
1385 Grob *
1386 Beam::last_normal_stem (Grob *me)
1388 extract_grob_set (me, "normal-stems", stems);
1389 return stems.size () ? stems.back () : 0;
1393 [TODO]
1395 handle rest under beam (do_post: beams are calculated now)
1396 what about combination of collisions and rest under beam.
1398 Should lookup
1400 rest -> stem -> beam -> interpolate_y_position ()
1402 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Beam, rest_collision_callback, 2, 1, "");
1404 Beam::rest_collision_callback (SCM smob, SCM prev_offset)
1406 Grob *rest = unsmob_grob (smob);
1407 if (scm_is_number (rest->get_property ("staff-position")))
1408 return scm_from_int (0);
1410 Real offset = robust_scm2double (prev_offset, 0.0);
1412 Grob *st = unsmob_grob (rest->get_object ("stem"));
1413 Grob *stem = st;
1414 if (!stem)
1415 return scm_from_double (0.0);
1416 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1417 if (!beam
1418 || !Beam::has_interface (beam)
1419 || !Beam::normal_stem_count (beam))
1420 return scm_from_double (0.0);
1422 Drul_array<Real> pos (robust_scm2drul (beam->get_property ("positions"),
1423 Drul_array<Real> (0,0)));
1425 Real staff_space = Staff_symbol_referencer::staff_space (rest);
1427 scale_drul (&pos, staff_space);
1429 Real dy = pos[RIGHT] - pos[LEFT];
1431 Drul_array<Grob*> visible_stems (first_normal_stem (beam),
1432 last_normal_stem (beam));
1433 extract_grob_set (beam, "stems", stems);
1435 Grob *common = common_refpoint_of_array (stems, beam, X_AXIS);
1437 Real x0 = visible_stems[LEFT]->relative_coordinate (common, X_AXIS);
1438 Real dx = visible_stems[RIGHT]->relative_coordinate (common, X_AXIS) - x0;
1439 Real slope = dy && dx ? dy / dx : 0;
1441 Direction d = get_grob_direction (stem);
1442 Real stem_y = pos[LEFT]
1443 + (stem->relative_coordinate (common, X_AXIS) - x0) * slope;
1445 Real beam_translation = get_beam_translation (beam);
1446 Real beam_thickness = Beam::get_thickness (beam);
1449 TODO: this is not strictly correct for 16th knee beams.
1451 int beam_count
1452 = Stem::beam_multiplicity (stem).length () + 1;
1454 Real height_of_my_beams = beam_thickness / 2
1455 + (beam_count - 1) * beam_translation;
1456 Real beam_y = stem_y - d * height_of_my_beams;
1458 Grob *common_y = rest->common_refpoint (beam, Y_AXIS);
1461 TODO: this is dubious, because this call needs the info we're
1462 computing right now.
1464 Interval rest_extent = rest->extent (common_y, Y_AXIS);
1465 rest_extent.translate (offset);
1467 Real rest_dim = rest_extent[d];
1468 Real minimum_distance
1469 = staff_space * (robust_scm2double (stem->get_property ("stemlet-length"), 0.0)
1470 + robust_scm2double (rest->get_property ("minimum-distance"), 0.0));
1472 Real shift = d * min (d * (beam_y - d * minimum_distance - rest_dim), 0.0);
1474 shift /= staff_space;
1475 Real rad = Staff_symbol_referencer::line_count (rest) * staff_space / 2;
1477 /* Always move discretely by half spaces */
1478 shift = ceil (fabs (shift * 2.0)) / 2.0 * sign (shift);
1480 /* Inside staff, move by whole spaces*/
1481 if ((rest_extent[d] + staff_space * shift) * d
1482 < rad
1483 || (rest_extent[-d] + staff_space * shift) * -d
1484 < rad)
1485 shift = ceil (fabs (shift)) * sign (shift);
1487 return scm_from_double (offset + staff_space * shift);
1490 bool
1491 Beam::is_knee (Grob *me)
1493 SCM k = me->get_property ("knee");
1494 if (scm_is_bool (k))
1495 return ly_scm2bool (k);
1497 bool knee = false;
1498 int d = 0;
1499 extract_grob_set (me, "stems", stems);
1500 for (vsize i = stems.size (); i--;)
1502 Direction dir = get_grob_direction (stems[i]);
1503 if (d && d != dir)
1505 knee = true;
1506 break;
1508 d = dir;
1511 me->set_property ("knee", ly_bool2scm (knee));
1513 return knee;
1516 bool
1517 Beam::is_cross_staff (Grob *me)
1519 extract_grob_set (me, "stems", stems);
1520 Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (me);
1521 for (vsize i = 0; i < stems.size (); i++)
1522 if (Staff_symbol_referencer::get_staff_symbol (stems[i]) != staff_symbol)
1523 return true;
1524 return false;
1527 MAKE_SCHEME_CALLBACK (Beam, calc_cross_staff, 1)
1529 Beam::calc_cross_staff (SCM smob)
1531 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1535 Beam::get_direction_beam_count (Grob *me, Direction d)
1537 extract_grob_set (me, "stems", stems);
1538 int bc = 0;
1540 for (vsize i = stems.size (); i--;)
1543 Should we take invisible stems into account?
1545 if (get_grob_direction (stems[i]) == d)
1546 bc = max (bc, (Stem::beam_multiplicity (stems[i]).length () + 1));
1549 return bc;
1552 ADD_INTERFACE (Beam,
1553 "A beam.\n"
1554 "\n"
1555 "The @code{thickness} property is the weight of beams,"
1556 " measured in staffspace. The @code{direction} property is"
1557 " not user-serviceable. Use the @code{direction} property"
1558 " of @code{Stem} instead.\n"
1559 "\n"
1560 "The following properties may be set in the @code{details}"
1561 " list.\n"
1562 "\n"
1563 "@table @code\n"
1564 "@item stem-length-demerit-factor\n"
1565 "Demerit factor used for inappropriate stem lengths.\n"
1566 "@item secondary-beam-demerit\n"
1567 "Demerit used in quanting calculations for multiple"
1568 " beams.\n"
1569 "@item region-size\n"
1570 "Size of region for checking quant scores.\n"
1571 "@item beam-eps\n"
1572 "Epsilon for beam quant code to check for presence"
1573 " in gap.\n"
1574 "@item stem-length-limit-penalty\n"
1575 "Penalty for differences in stem lengths on a beam.\n"
1576 "@item damping-direction-penalty\n"
1577 "Demerit penalty applied when beam direction is different"
1578 " from damping direction.\n"
1579 "@item hint-direction-penalty\n"
1580 "Demerit penalty applied when beam direction is different"
1581 " from damping direction, but damping slope is"
1582 " <= @code{round-to-zero-slope}.\n"
1583 "@item musical-direction-factor\n"
1584 "Demerit scaling factor for difference between"
1585 " beam slope and music slope.\n"
1586 "@item ideal-slope-factor\n"
1587 "Demerit scaling factor for difference between"
1588 " beam slope and damping slope.\n"
1589 "@item round-to-zero-slope\n"
1590 "Damping slope which is considered zero for purposes of"
1591 " calculating direction penalties.\n"
1592 "@end table\n",
1594 /* properties */
1595 "annotation "
1596 "auto-knee-gap "
1597 "beamed-stem-shorten "
1598 "beaming "
1599 "break-overshoot "
1600 "clip-edges "
1601 "concaveness "
1602 "damping "
1603 "details "
1604 "direction "
1605 "gap "
1606 "gap-count "
1607 "grow-direction "
1608 "inspect-quants "
1609 "knee "
1610 "length-fraction "
1611 "least-squares-dy "
1612 "neutral-direction "
1613 "normal-stems "
1614 "positions "
1615 "quant-score "
1616 "quantized-positions "
1617 "shorten "
1618 "stems "
1619 "thickness "