2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
7 TODO: This is way too hairy
11 Stem-end, chord-start, etc. is all confusing naming.
13 LilyPond is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 LilyPond is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
28 Note that several internal functions have a calc_beam bool argument.
29 This argument means: "If set, acknowledge the fact that there is a beam
30 and deal with it. If not, give me the measurements as if there is no beam."
31 Most pure functions are called WITHOUT calc_beam, whereas non-pure functions
32 are called WITH calc_beam.
34 The only exception to this is ::pure_height, which calls internal_pure_height
35 with "true" for calc_beam in order to trigger the calculations of other
36 pure heights in case there is a beam. It passes false, however, to
37 internal_height and internal_pure_height for all subsequent iterations.
43 #include <cmath> // rint
47 #include "directional-element-interface.hh"
48 #include "dot-column.hh"
49 #include "font-interface.hh"
50 #include "international.hh"
53 #include "note-head.hh"
54 #include "output-def.hh"
55 #include "paper-column.hh"
56 #include "pointer-group-interface.hh"
58 #include "rhythmic-head.hh"
59 #include "side-position-interface.hh"
60 #include "staff-symbol-referencer.hh"
61 #include "stem-tremolo.hh"
65 Stem::set_beaming (Grob *me, int beam_count, Direction d)
67 SCM pair = me->get_property ("beaming");
69 if (!scm_is_pair (pair))
71 pair = scm_cons (SCM_EOL, SCM_EOL);
72 me->set_property ("beaming", pair);
75 SCM lst = index_get_cell (pair, d);
77 for (int i = 0; i < beam_count; i++)
78 lst = scm_cons (scm_from_int (i), lst);
82 index_set_cell (pair, d, lst);
86 Stem::get_beaming (Grob *me, Direction d)
88 SCM pair = me->get_property ("beaming");
89 if (!scm_is_pair (pair))
92 SCM lst = index_get_cell (pair, d);
94 int len = scm_ilength (lst);
99 Stem::head_positions (Grob *me)
103 Drul_array<Grob *> e (extremal_heads (me));
104 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
105 Staff_symbol_referencer::get_position (e[UP]));
111 Stem::chord_start_y (Grob *me)
113 Interval hp = head_positions (me);
115 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
121 Stem::set_stem_positions (Grob *me, Real se)
124 Direction d = get_grob_direction (me);
126 Grob *beam = unsmob_grob (me->get_object ("beam"));
127 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
128 me->warning (_ ("weird stem size, check for narrow beams"));
130 // trigger note collision mechanisms
131 Real stem_beg = internal_calc_stem_begin_position (me, false);
132 Real staff_space = Staff_symbol_referencer::staff_space (me);
133 Real half_space = staff_space * 0.5;
136 height[-d] = stem_beg * half_space;
137 height[d] = se * half_space + beam_end_corrective (me);
139 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
141 bool stemlet = stemlet_length > 0.0;
143 Grob *lh = get_reference_head (me);
149 Real beam_translation = Beam::get_beam_translation (beam);
150 Real beam_thickness = Beam::get_beam_thickness (beam);
151 int beam_count = beam_multiplicity (me).length () + 1;
153 height[-d] = (height[d] - d
154 * (0.5 * beam_thickness
155 + beam_translation * max (0, (beam_count - 1))
158 else if (!stemlet && beam)
159 height[-d] = height[d];
160 else if (stemlet && !beam)
161 me->programming_error ("Can't have a stemlet without a beam.");
164 me->set_property ("stem-begin-position", scm_from_double (height[-d] * 2 / staff_space));
165 me->set_property ("length", scm_from_double (height.length () * 2 / staff_space));
168 /* Note head that determines hshift for upstems
169 WARNING: triggers direction */
171 Stem::support_head (Grob *me)
173 extract_grob_set (me, "note-heads", heads);
174 if (heads.size () == 1)
177 return first_head (me);
181 Stem::head_count (Grob *me)
183 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
186 /* The note head which forms one end of the stem.
187 WARNING: triggers direction */
189 Stem::first_head (Grob *me)
191 Direction d = get_grob_direction (me);
193 return extremal_heads (me)[-d];
197 /* The note head opposite to the first head. */
199 Stem::last_head (Grob *me)
201 Direction d = get_grob_direction (me);
203 return extremal_heads (me)[d];
208 START is part where stem reaches `last' head.
210 This function returns a drul with (bottom-head, top-head).
213 Stem::extremal_heads (Grob *me)
215 const int inf = INT_MAX;
216 Drul_array<int> extpos;
220 Drul_array<Grob *> exthead (0, 0);
221 extract_grob_set (me, "note-heads", heads);
223 for (vsize i = heads.size (); i--;)
226 int p = Staff_symbol_referencer::get_rounded_position (n);
231 if (d * p > d * extpos[d])
237 while (flip (&d) != DOWN);
242 /* The staff positions, in ascending order.
243 * If FILTER, include the main column of noteheads only */
245 Stem::note_head_positions (Grob *me, bool filter)
248 extract_grob_set (me, "note-heads", heads);
249 Grob *xref = common_refpoint_of_array (heads, me, X_AXIS);
251 for (vsize i = heads.size (); i--;)
255 && n->relative_coordinate (xref, X_AXIS) != 0.0)
258 int p = Staff_symbol_referencer::get_rounded_position (n);
262 vector_sort (ps, less<int> ());
267 Stem::add_head (Grob *me, Grob *n)
269 n->set_object ("stem", me->self_scm ());
271 if (Note_head::has_interface (n))
272 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
273 else if (Rest::has_interface (n))
274 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
278 Stem::is_invisible (Grob *me)
280 return !is_normal_stem (me)
281 && (robust_scm2double (me->get_property ("stemlet-length"),
286 Stem::is_normal_stem (Grob *me)
288 if (!head_count (me))
291 extract_grob_set (me, "note-heads", heads);
292 SCM style = heads[0]->get_property ("style");
293 return style != ly_symbol2scm ("kievan") && scm_to_int (me->get_property ("duration-log")) >= 1;
296 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
298 Stem::pure_height (SCM smob,
302 Grob *me = unsmob_grob (smob);
303 return ly_interval2scm (internal_pure_height (me, true));
307 Stem::internal_pure_height (Grob *me, bool calc_beam)
309 if (!is_normal_stem (me))
310 return Interval (0.0, 0.0);
312 Grob *beam = unsmob_grob (me->get_object ("beam"));
314 Interval iv = internal_height (me, false);
321 Direction dir = get_grob_direction (me);
324 overshoot[d] = d == dir ? dir * infinity_f : iv[d];
325 while (flip (&d) != DOWN);
327 vector<Interval> heights;
328 vector<Grob *> my_stems;
329 extract_grob_set (beam, "normal-stems", normal_stems);
330 for (vsize i = 0; i < normal_stems.size (); i++)
331 if (get_grob_direction (normal_stems[i]) == dir)
333 if (normal_stems[i] != me)
334 heights.push_back (Stem::internal_pure_height (normal_stems[i], false));
336 heights.push_back (iv);
337 my_stems.push_back (normal_stems[i]);
339 //iv.unite (heights.back ());
340 // look for cross staff effects
342 Grob *common = common_refpoint_of_array (my_stems, me, Y_AXIS);
343 Real min_pos = infinity_f;
344 Real max_pos = -infinity_f;
345 for (vsize i = 0; i < my_stems.size (); i++)
347 coords.push_back (my_stems[i]->pure_relative_y_coordinate (common, 0, INT_MAX));
348 min_pos = min (min_pos, coords[i]);
349 max_pos = max (max_pos, coords[i]);
351 for (vsize i = 0; i < heights.size (); i++)
353 heights[i][dir] += dir == DOWN
354 ? coords[i] - max_pos
355 : coords[i] - min_pos;
358 for (vsize i = 0; i < heights.size (); i++) iv.unite (heights[i]);
360 for (vsize i = 0; i < my_stems.size (); i++)
361 cache_pure_height (my_stems[i], iv, heights[i]);
362 iv.intersect (overshoot);
369 Stem::cache_pure_height (Grob *me, Interval iv, Interval my_iv)
372 Direction dir = get_grob_direction (me);
375 overshoot[d] = d == dir ? dir * infinity_f : my_iv[d];
376 while (flip (&d) != DOWN);
378 iv.intersect (overshoot);
379 dynamic_cast<Item *> (me)->cache_pure_height (iv);
382 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
384 Stem::calc_stem_end_position (SCM smob)
386 Grob *me = unsmob_grob (smob);
387 return scm_from_double (internal_calc_stem_end_position (me, true));
390 MAKE_SCHEME_CALLBACK (Stem, pure_calc_stem_end_position, 3)
392 Stem::pure_calc_stem_end_position (SCM smob,
396 Grob *me = unsmob_grob (smob);
397 return scm_from_double (internal_calc_stem_end_position (me, false));
401 Stem::internal_calc_stem_end_position (Grob *me, bool calc_beam)
403 if (!head_count (me))
406 Grob *beam = get_beam (me);
407 Real ss = Staff_symbol_referencer::staff_space (me);
408 Direction dir = get_grob_direction (me);
410 if (beam && calc_beam)
412 (void) beam->get_property ("quantized-positions");
413 return robust_scm2double (me->get_property ("length"), 0.0)
414 + dir * robust_scm2double (me->get_property ("stem-begin-position"), 0.0);
419 /* WARNING: IN HALF SPACES */
420 SCM details = me->get_property ("details");
421 int durlog = duration_log (me);
423 Real staff_rad = Staff_symbol_referencer::staff_radius (me);
425 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
427 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
429 /* Stems in unnatural (forced) direction should be shortened,
430 according to [Roush & Gourlay] */
431 Interval hp = head_positions (me);
432 if (dir && dir * hp[dir] >= 0)
434 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
435 SCM scm_shorten = scm_is_pair (sshorten)
436 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
437 Real shorten_property = 2 * robust_scm2double (scm_shorten, 0);
438 /* change in length between full-size and shortened stems is executed gradually.
439 "transition area" = stems between full-sized and fully-shortened.
441 Real quarter_stem_length = 2 * scm_to_double (robust_list_ref (0, s));
442 /* shortening_step = difference in length between consecutive stem lengths
443 in transition area. The bigger the difference between full-sized
444 and shortened stems, the bigger shortening_step is.
445 (but not greater than 1/2 and not smaller than 1/4).
446 value 6 is heuristic; it determines the suggested transition slope steepnesas.
448 Real shortening_step = min (max (0.25, (shorten_property / 6)), 0.5);
449 /* Shortening of unflagged stems should begin on the first stem that sticks
450 more than 1 staffspace (2 units) out of the staff.
451 Shortening of flagged stems begins in the same moment as unflagged ones,
452 but not earlier than on the middle line note.
454 Real which_step = (min (1.0, quarter_stem_length - (2 * staff_rad) - 2.0)) + abs (hp[dir]);
455 Real shorten = min (max (0.0, (shortening_step * which_step)), shorten_property);
460 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
463 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
464 if (t_flag && (!unsmob_grob (me->get_object ("beam")) || !calc_beam))
466 /* Crude hack: add extra space if tremolo flag is there.
468 We can't do this for the beam, since we get into a loop
469 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
472 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
474 /* We don't want to add the whole extent of the flag because the trem
475 and the flag can overlap partly. beam_translation gives a good
479 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
480 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
481 minlen += 2 * (durlog - 1.5) * beam_trans;
483 /* up-stems need even a little more space to avoid collisions. This
484 needs to be in sync with the tremolo positioning code in
485 Stem_tremolo::print */
487 minlen += beam_trans;
489 length = max (length, minlen + 1.0);
492 Real stem_end = dir ? hp[dir] + dir * length : 0;
494 /* TODO: change name to extend-stems to staff/center/'() */
495 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
496 if (!no_extend && dir * stem_end < 0)
502 /* The log of the duration (Number of hooks on the flag minus two) */
504 Stem::duration_log (Grob *me)
506 SCM s = me->get_property ("duration-log");
507 return (scm_is_number (s)) ? scm_to_int (s) : 2;
510 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
512 Stem::calc_positioning_done (SCM smob)
514 Grob *me = unsmob_grob (smob);
515 if (!head_count (me))
518 me->set_property ("positioning-done", SCM_BOOL_T);
520 extract_grob_set (me, "note-heads", ro_heads);
521 vector<Grob *> heads (ro_heads);
522 vector_sort (heads, position_less);
523 Direction dir = get_grob_direction (me);
528 Real thick = thickness (me);
530 Grob *hed = support_head (me);
533 programming_error ("Stem dir must be up or down.");
535 set_grob_direction (me, dir);
538 bool is_harmonic_centered = false;
539 for (vsize i = 0; i < heads.size (); i++)
540 is_harmonic_centered = is_harmonic_centered
541 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
542 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
544 Real w = hed->extent (hed, X_AXIS)[dir];
545 for (vsize i = 0; i < heads.size (); i++)
547 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
549 if (is_harmonic_centered)
551 = hed->extent (hed, X_AXIS).linear_combination (CENTER)
552 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
554 heads[i]->translate_axis (amount, X_AXIS);
557 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
558 for (vsize i = 1; i < heads.size (); i++)
560 Real p = Staff_symbol_referencer::get_position (heads[i]);
561 Real dy = fabs (lastpos - p);
564 dy should always be 0.5, 0.0, 1.0, but provide safety margin
571 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
573 Direction d = get_grob_direction (me);
575 Reversed head should be shifted ell-thickness, but this
576 looks too crowded, so we only shift ell-0.5*thickness.
578 This leads to assymetry: Normal heads overlap the
579 stem 100% whereas reversed heads only overlaps the
583 Real reverse_overlap = 0.5;
584 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
587 if (is_invisible (me))
588 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
593 For some cases we should kern some more: when the
594 distance between the next or prev note is too large, we'd
595 get large white gaps, eg.
616 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
618 Stem::calc_direction (SCM smob)
620 Grob *me = unsmob_grob (smob);
621 Direction dir = CENTER;
622 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
624 SCM ignore_me = beam->get_property ("direction");
626 dir = get_grob_direction (me);
630 SCM dd = me->get_property ("default-direction");
633 return me->get_property ("neutral-direction");
636 return scm_from_int (dir);
639 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
641 Stem::calc_default_direction (SCM smob)
643 Grob *me = unsmob_grob (smob);
645 Direction dir = CENTER;
646 int staff_center = 0;
647 Interval hp = head_positions (me);
650 int udistance = (int) (UP * hp[UP] - staff_center);
651 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
653 dir = Direction (sign (ddistance - udistance));
656 return scm_from_int (dir);
659 // note - height property necessary to trigger quantized beam positions
660 // otherwise, we could just use Grob::stencil_height_proc
661 MAKE_SCHEME_CALLBACK (Stem, height, 1);
663 Stem::height (SCM smob)
665 Grob *me = unsmob_grob (smob);
666 return ly_interval2scm (internal_height (me, true));
670 Stem::get_reference_head (Grob *me)
672 return to_boolean (me->get_property ("avoid-note-head"))
678 Stem::beam_end_corrective (Grob *me)
680 Grob *beam = unsmob_grob (me->get_object ("beam"));
681 Direction dir = get_grob_direction (me);
686 programming_error ("no stem direction");
689 return dir * Beam::get_beam_thickness (beam) * 0.5;
695 Stem::internal_height (Grob *me, bool calc_beam)
697 Grob *beam = get_beam (me);
698 if (!is_valid_stem (me) && ! beam)
701 Direction dir = get_grob_direction (me);
703 if (beam && calc_beam)
705 /* trigger set-stem-lengths. */
706 (void) beam->get_property ("quantized-positions");
709 Real y1 = robust_scm2double ((calc_beam
710 ? me->get_property ("stem-begin-position")
711 : me->get_pure_property ("stem-begin-position", 0, INT_MAX)),
714 Real y2 = dir * robust_scm2double ((calc_beam
715 ? me->get_property ("length")
716 : me->get_pure_property ("length", 0, INT_MAX)),
720 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
722 Interval stem_y = Interval (min (y1, y2), max (y2, y1)) * half_space;
727 MAKE_SCHEME_CALLBACK (Stem, width, 1);
731 Grob *me = unsmob_grob (e);
735 if (is_invisible (me))
739 r = Interval (-1, 1);
740 r *= thickness (me) / 2;
743 return ly_interval2scm (r);
747 Stem::thickness (Grob *me)
749 return scm_to_double (me->get_property ("thickness"))
750 * Staff_symbol_referencer::line_thickness (me);
753 MAKE_SCHEME_CALLBACK (Stem, calc_stem_begin_position, 1);
755 Stem::calc_stem_begin_position (SCM smob)
757 Grob *me = unsmob_grob (smob);
758 return scm_from_double (internal_calc_stem_begin_position (me, true));
761 MAKE_SCHEME_CALLBACK (Stem, pure_calc_stem_begin_position, 3);
763 Stem::pure_calc_stem_begin_position (SCM smob,
767 Grob *me = unsmob_grob (smob);
768 return scm_from_double (internal_calc_stem_begin_position (me, false));
772 Stem::internal_calc_stem_begin_position (Grob *me, bool calc_beam)
774 Grob *beam = get_beam (me);
775 Real ss = Staff_symbol_referencer::staff_space (me);
776 if (beam && calc_beam)
778 (void) beam->get_property ("quantized-positions");
779 return robust_scm2double (me->get_property ("stem-begin-position"), 0.0);
782 Direction d = get_grob_direction (me);
783 Grob *lh = get_reference_head (me);
788 Real pos = Staff_symbol_referencer::get_position (lh);
790 if (Grob *head = support_head (me))
792 Interval head_height = head->extent (head, Y_AXIS);
793 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
795 y_attach = head_height.linear_combination (y_attach);
796 pos += d * y_attach * 2 / ss;
803 Stem::is_valid_stem (Grob *me)
805 /* TODO: make the stem start a direction ?
806 This is required to avoid stems passing in tablature chords. */
807 Grob *lh = get_reference_head (me);
808 Grob *beam = unsmob_grob (me->get_object ("beam"));
813 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
816 if (is_invisible (me))
822 MAKE_SCHEME_CALLBACK (Stem, print, 1);
824 Stem::print (SCM smob)
826 Grob *me = unsmob_grob (smob);
827 if (!is_valid_stem (me))
830 Direction dir = get_grob_direction (me);
831 Real y1 = robust_scm2double (me->get_property ("stem-begin-position"), 0.0);
832 Real y2 = dir * robust_scm2double (me->get_property ("length"), 0.0) + y1;
834 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
836 Interval stem_y = Interval (min (y1, y2), max (y2, y1)) * half_space;
838 stem_y[dir] -= beam_end_corrective (me);
841 Real stem_width = thickness (me);
843 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
845 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
849 Stencil ss = Lookup::round_filled_box (b, blot);
850 mol.add_stencil (ss);
852 return mol.smobbed_copy ();
856 move the stem to right of the notehead if it is up.
858 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
860 Stem::offset_callback (SCM smob)
862 Grob *me = unsmob_grob (smob);
864 extract_grob_set (me, "rests", rests);
867 Grob *rest = rests.back ();
868 Real r = rest->extent (rest, X_AXIS).center ();
869 return scm_from_double (r);
872 if (Grob *f = first_head (me))
874 Interval head_wid = f->extent (f, X_AXIS);
877 if (is_invisible (me))
880 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
882 Direction d = get_grob_direction (me);
883 Real real_attach = head_wid.linear_combination (d * attach);
884 Real r = real_attach;
886 /* If not centered: correct for stem thickness. */
887 string style = robust_symbol2string (f->get_property ("style"), "default");
888 if (attach && style != "mensural"
889 && style != "neomensural"
890 && style != "petrucci")
892 Real rule_thick = thickness (me);
893 r += -d * rule_thick * 0.5;
895 return scm_from_double (r);
898 programming_error ("Weird stem.");
899 return scm_from_double (0.0);
903 Stem::get_beam (Grob *me)
905 SCM b = me->get_object ("beam");
906 return dynamic_cast<Spanner *> (unsmob_grob (b));
910 Stem::get_stem_info (Grob *me)
913 si.dir_ = get_grob_direction (me);
915 SCM scm_info = me->get_property ("stem-info");
916 si.ideal_y_ = scm_to_double (scm_car (scm_info));
917 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
921 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
923 Stem::calc_stem_info (SCM smob)
925 Grob *me = unsmob_grob (smob);
926 Direction my_dir = get_grob_direction (me);
930 programming_error ("no stem dir set");
934 Real staff_space = Staff_symbol_referencer::staff_space (me);
935 Grob *beam = get_beam (me);
939 (void) beam->get_property ("beaming");
942 Real beam_translation = Beam::get_beam_translation (beam);
943 Real beam_thickness = Beam::get_beam_thickness (beam);
944 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
946 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
948 /* Simple standard stem length */
949 SCM details = me->get_property ("details");
950 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
953 = (scm_is_pair (lengths)
954 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
958 stem only extends to center of beam
960 - 0.5 * beam_thickness)
963 /* Condition: sane minimum free stem length (chord to beams) */
964 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
967 Real ideal_minimum_free
968 = (scm_is_pair (lengths)
969 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
974 Real height_of_my_trem = 0.0;
975 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
979 = Stem_tremolo::vertical_length (trem)
980 /* hack a bit of space around the trem. */
985 It seems that also for ideal minimum length, we must use
986 the maximum beam count (for this direction):
988 \score { \relative c'' { a8[ a32] } }
990 must be horizontal. */
991 Real height_of_my_beams = beam_thickness
992 + (beam_count - 1) * beam_translation;
994 Real ideal_minimum_length = ideal_minimum_free
997 /* stem only extends to center of beam */
998 - 0.5 * beam_thickness;
1000 ideal_length = max (ideal_length, ideal_minimum_length);
1002 /* Convert to Y position, calculate for dir == UP */
1004 = /* staff positions */
1005 head_positions (me)[my_dir] * 0.5
1006 * my_dir * staff_space;
1007 Real ideal_y = note_start + ideal_length;
1009 /* Conditions for Y position */
1011 /* Lowest beam of (UP) beam must never be lower than second staffline
1015 Although this (additional) rule is probably correct,
1016 I expect that highest beam (UP) should also never be lower
1017 than middle staffline, just as normal stems.
1021 Obviously not for grace beams.
1023 Also, not for knees. Seems to be a good thing. */
1024 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
1025 bool is_knee = Beam::is_knee (beam);
1026 if (!no_extend && !is_knee)
1028 /* Highest beam of (UP) beam must never be lower than middle
1030 ideal_y = max (ideal_y, 0.0);
1031 /* Lowest beam of (UP) beam must never be lower than second staffline */
1032 ideal_y = max (ideal_y, (-staff_space
1033 - beam_thickness + height_of_my_beams));
1036 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
1038 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
1042 = (scm_is_pair (bemfl)
1043 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1048 Real minimum_length = max (minimum_free, height_of_my_trem)
1049 + height_of_my_beams
1050 /* stem only extends to center of beam */
1051 - 0.5 * beam_thickness;
1054 Real minimum_y = note_start + minimum_length;
1055 Real shortest_y = minimum_y * my_dir;
1057 return scm_list_2 (scm_from_double (ideal_y),
1058 scm_from_double (shortest_y));
1062 Stem::beam_multiplicity (Grob *stem)
1064 SCM beaming = stem->get_property ("beaming");
1065 Slice le = int_list_to_slice (scm_car (beaming));
1066 Slice ri = int_list_to_slice (scm_cdr (beaming));
1072 Stem::is_cross_staff (Grob *stem)
1074 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1075 return beam && Beam::is_cross_staff (beam);
1078 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1080 Stem::calc_cross_staff (SCM smob)
1082 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1086 Stem::flag (Grob *me)
1088 return unsmob_grob (me->get_object ("flag"));
1091 /* FIXME: Too many properties */
1092 ADD_INTERFACE (Stem,
1093 "The stem represents the graphical stem. In addition, it"
1094 " internally connects note heads, beams, and tremolos. Rests"
1095 " and whole notes have invisible stems.\n"
1097 "The following properties may be set in the @code{details}"
1101 "@item beamed-lengths\n"
1102 "List of stem lengths given beam multiplicity.\n"
1103 "@item beamed-minimum-free-lengths\n"
1104 "List of normal minimum free stem lengths (chord to beams)"
1105 " given beam multiplicity.\n"
1106 "@item beamed-extreme-minimum-free-lengths\n"
1107 "List of extreme minimum free stem lengths (chord to beams)"
1108 " given beam multiplicity.\n"
1110 "Default stem lengths. The list gives a length for each"
1112 "@item stem-shorten\n"
1113 "How much a stem in a forced direction should be shortened."
1114 " The list gives an amount depending on the number of flags"
1122 "beamlet-default-length "
1123 "beamlet-max-length-proportion "
1124 "default-direction "
1134 "neutral-direction "
1139 "stem-begin-position "
1147 /****************************************************************/
1149 Stem_info::Stem_info ()
1151 ideal_y_ = shortest_y_ = 0;
1156 Stem_info::scale (Real x)