2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2011 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/>.
30 #include <cmath> // rint
34 #include "directional-element-interface.hh"
35 #include "dot-column.hh"
36 #include "font-interface.hh"
37 #include "international.hh"
40 #include "note-head.hh"
41 #include "output-def.hh"
42 #include "paper-column.hh"
43 #include "pointer-group-interface.hh"
45 #include "rhythmic-head.hh"
46 #include "side-position-interface.hh"
47 #include "staff-symbol-referencer.hh"
48 #include "stem-tremolo.hh"
52 Stem::set_beaming (Grob *me, int beam_count, Direction d)
54 SCM pair = me->get_property ("beaming");
56 if (!scm_is_pair (pair))
58 pair = scm_cons (SCM_EOL, SCM_EOL);
59 me->set_property ("beaming", pair);
62 SCM lst = index_get_cell (pair, d);
64 for (int i = 0; i < beam_count; i++)
65 lst = scm_cons (scm_from_int (i), lst);
69 index_set_cell (pair, d, lst);
73 Stem::get_beaming (Grob *me, Direction d)
75 SCM pair = me->get_property ("beaming");
76 if (!scm_is_pair (pair))
79 SCM lst = index_get_cell (pair, d);
81 int len = scm_ilength (lst);
86 Stem::head_positions (Grob *me)
90 Drul_array<Grob *> e (extremal_heads (me));
91 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
92 Staff_symbol_referencer::get_position (e[UP]));
98 Stem::chord_start_y (Grob *me)
100 Interval hp = head_positions (me);
102 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
110 Stem::set_stemend (Grob *me, Real se)
113 Direction d = get_grob_direction (me);
115 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
116 me->warning (_ ("weird stem size, check for narrow beams"));
118 me->set_property ("stem-end-position", scm_from_double (se));
121 /* Note head that determines hshift for upstems
122 WARNING: triggers direction */
124 Stem::support_head (Grob *me)
126 extract_grob_set (me, "note-heads", heads);
127 if (heads.size () == 1)
130 return first_head (me);
134 Stem::head_count (Grob *me)
136 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
139 /* The note head which forms one end of the stem.
140 WARNING: triggers direction */
142 Stem::first_head (Grob *me)
144 Direction d = get_grob_direction (me);
146 return extremal_heads (me)[-d];
150 /* The note head opposite to the first head. */
152 Stem::last_head (Grob *me)
154 Direction d = get_grob_direction (me);
156 return extremal_heads (me)[d];
161 START is part where stem reaches `last' head.
163 This function returns a drul with (bottom-head, top-head).
166 Stem::extremal_heads (Grob *me)
168 const int inf = INT_MAX;
169 Drul_array<int> extpos;
173 Drul_array<Grob *> exthead (0, 0);
174 extract_grob_set (me, "note-heads", heads);
176 for (vsize i = heads.size (); i--;)
179 int p = Staff_symbol_referencer::get_rounded_position (n);
184 if (d * p > d * extpos[d])
190 while (flip (&d) != DOWN);
195 /* The positions, in ascending order. */
197 Stem::note_head_positions (Grob *me)
200 extract_grob_set (me, "note-heads", heads);
202 for (vsize i = heads.size (); i--;)
205 int p = Staff_symbol_referencer::get_rounded_position (n);
210 vector_sort (ps, less<int> ());
215 Stem::add_head (Grob *me, Grob *n)
217 n->set_object ("stem", me->self_scm ());
219 if (Note_head::has_interface (n))
220 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
221 else if (Rest::has_interface (n))
222 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
226 Stem::is_invisible (Grob *me)
228 return !is_normal_stem (me)
229 && (robust_scm2double (me->get_property ("stemlet-length"),
235 Stem::is_normal_stem (Grob *me)
237 return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
241 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
243 Stem::pure_height (SCM smob,
247 Grob *me = unsmob_grob (smob);
250 if (!is_normal_stem (me))
251 return ly_interval2scm (iv);
253 Real ss = Staff_symbol_referencer::staff_space (me);
254 Real rad = Staff_symbol_referencer::staff_radius (me);
256 if (!to_boolean (me->get_property ("cross-staff")))
258 Real len_in_halfspaces;
259 SCM user_set_len_scm = me->get_property_data ("length");
260 if (scm_is_number (user_set_len_scm))
261 len_in_halfspaces = scm_to_double (user_set_len_scm);
263 len_in_halfspaces = scm_to_double (calc_length (smob));
264 Real len = len_in_halfspaces * ss / 2;
265 Direction dir = get_grob_direction (me);
267 Interval hp = head_positions (me);
269 iv = Interval (0, len);
271 iv = Interval (-len, 0);
275 iv.translate (hp[dir] * ss / 2);
276 iv.add_point (hp[-dir] * ss / 2);
279 /* extend the stem (away from the head) to cover the staff */
281 iv[UP] = max (iv[UP], rad * ss);
283 iv[DOWN] = min (iv[DOWN], -rad * ss);
286 iv = Interval (-rad * ss, rad * ss);
288 return ly_interval2scm (iv);
291 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
293 Stem::calc_stem_end_position (SCM smob)
295 Grob *me = unsmob_grob (smob);
297 if (!head_count (me))
298 return scm_from_double (0.0);
300 if (Grob *beam = get_beam (me))
302 (void) beam->get_property ("quantized-positions");
303 return me->get_property ("stem-end-position");
308 /* WARNING: IN HALF SPACES */
309 Real length = robust_scm2double (me->get_property ("length"), 7);
311 Direction dir = get_grob_direction (me);
312 Interval hp = head_positions (me);
313 Real stem_end = dir ? hp[dir] + dir * length : 0;
315 /* TODO: change name to extend-stems to staff/center/'() */
316 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
317 if (!no_extend && dir * stem_end < 0)
320 return scm_from_double (stem_end);
323 /* Length is in half-spaces (or: positions) here. */
324 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
326 Stem::calc_length (SCM smob)
328 Grob *me = unsmob_grob (smob);
330 SCM details = me->get_property ("details");
331 int durlog = duration_log (me);
333 Real ss = Staff_symbol_referencer::staff_space (me);
334 Real staff_rad = Staff_symbol_referencer::staff_radius (me);
336 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
338 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
340 Direction dir = get_grob_direction (me);
342 /* Stems in unnatural (forced) direction should be shortened,
343 according to [Roush & Gourlay] */
344 Interval hp = head_positions (me);
345 if (dir && dir * hp[dir] >= 0)
347 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
348 SCM scm_shorten = scm_is_pair (sshorten)
349 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
350 Real shorten_property = 2 * robust_scm2double (scm_shorten, 0);
351 /* change in length between full-size and shortened stems is executed gradually.
352 "transition area" = stems between full-sized and fully-shortened.
354 Real quarter_stem_length = 2 * scm_to_double (robust_list_ref (0, s));
355 /* shortening_step = difference in length between consecutive stem lengths
356 in transition area. The bigger the difference between full-sized
357 and shortened stems, the bigger shortening_step is.
358 (but not greater than 1/2 and not smaller than 1/4).
359 value 6 is heuristic; it determines the suggested transition slope steepnesas.
361 Real shortening_step = min (max (0.25, (shorten_property / 6)), 0.5);
362 /* Shortening of unflagged stems should begin on the first stem that sticks
363 more than 1 staffspace (2 units) out of the staff.
364 Shortening of flagged stems begins in the same moment as unflagged ones,
365 but not earlier than on the middle line note.
367 Real which_step = (min (1.0, quarter_stem_length - (2 * staff_rad) - 2.0)) + abs(hp[dir]);
368 Real shorten = min (max (0.0, (shortening_step * which_step)), shorten_property);
373 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
376 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
377 if (t_flag && !unsmob_grob (me->get_object ("beam")))
379 /* Crude hack: add extra space if tremolo flag is there.
381 We can't do this for the beam, since we get into a loop
382 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
385 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
387 /* We don't want to add the whole extent of the flag because the trem
388 and the flag can overlap partly. beam_translation gives a good
392 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
393 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
394 minlen += 2 * (durlog - 1.5) * beam_trans;
396 /* up-stems need even a little more space to avoid collisions. This
397 needs to be in sync with the tremolo positioning code in
398 Stem_tremolo::print */
400 minlen += beam_trans;
402 length = max (length, minlen + 1.0);
405 return scm_from_double (length);
407 /* The log of the duration (Number of hooks on the flag minus two) */
409 Stem::duration_log (Grob *me)
411 SCM s = me->get_property ("duration-log");
412 return (scm_is_number (s)) ? scm_to_int (s) : 2;
415 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
417 Stem::calc_positioning_done (SCM smob)
419 Grob *me = unsmob_grob (smob);
420 if (!head_count (me))
423 me->set_property ("positioning-done", SCM_BOOL_T);
425 extract_grob_set (me, "note-heads", ro_heads);
426 vector<Grob*> heads (ro_heads);
427 vector_sort (heads, position_less);
428 Direction dir = get_grob_direction (me);
433 Real thick = thickness (me);
435 Grob *hed = support_head (me);
438 programming_error ("Stem dir must be up or down.");
440 set_grob_direction (me, dir);
443 bool is_harmonic_centered = false;
444 for (vsize i = 0; i < heads.size (); i++)
445 is_harmonic_centered = is_harmonic_centered
446 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
447 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
449 Real w = hed->extent (hed, X_AXIS)[dir];
450 for (vsize i = 0; i < heads.size (); i++)
452 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
454 if (is_harmonic_centered)
456 hed->extent (hed, X_AXIS).linear_combination (CENTER)
457 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
459 heads[i]->translate_axis (amount, X_AXIS);
462 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
463 for (vsize i = 1; i < heads.size (); i++)
465 Real p = Staff_symbol_referencer::get_position (heads[i]);
466 Real dy = fabs (lastpos- p);
469 dy should always be 0.5, 0.0, 1.0, but provide safety margin
476 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
478 Direction d = get_grob_direction (me);
480 Reversed head should be shifted ell-thickness, but this
481 looks too crowded, so we only shift ell-0.5*thickness.
483 This leads to assymetry: Normal heads overlap the
484 stem 100% whereas reversed heads only overlaps the
488 Real reverse_overlap = 0.5;
489 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
492 if (is_invisible (me))
493 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
498 For some cases we should kern some more: when the
499 distance between the next or prev note is too large, we'd
500 get large white gaps, eg.
521 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
523 Stem::calc_direction (SCM smob)
525 Grob *me = unsmob_grob (smob);
526 Direction dir = CENTER;
527 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
529 SCM ignore_me = beam->get_property ("direction");
531 dir = get_grob_direction (me);
535 SCM dd = me->get_property ("default-direction");
538 return me->get_property ("neutral-direction");
541 return scm_from_int (dir);
544 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
546 Stem::calc_default_direction (SCM smob)
548 Grob *me = unsmob_grob (smob);
550 Direction dir = CENTER;
551 int staff_center = 0;
552 Interval hp = head_positions (me);
555 int udistance = (int) (UP * hp[UP] - staff_center);
556 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
558 dir = Direction (sign (ddistance - udistance));
561 return scm_from_int (dir);
565 MAKE_SCHEME_CALLBACK (Stem, height, 1);
567 Stem::height (SCM smob)
569 Grob *me = unsmob_grob (smob);
570 if (!is_normal_stem (me))
571 return ly_interval2scm (Interval ());
573 Direction dir = get_grob_direction (me);
575 Grob *beam = get_beam (me);
578 /* trigger set-stem-lengths. */
579 beam->get_property ("quantized-positions");
583 Can't get_stencil (), since that would cache stencils too early.
584 This causes problems with beams.
586 Stencil *stencil = unsmob_stencil (print (smob));
587 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
592 programming_error ("no stem direction");
595 iv[dir] += dir * Beam::get_beam_thickness (beam) * 0.5;
598 return ly_interval2scm (iv);
602 Stem::stem_end_position (Grob *me)
604 return robust_scm2double (me->get_property ("stem-end-position"), 0);
607 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
609 Stem::calc_flag (SCM smob)
611 Grob *me = unsmob_grob (smob);
613 int log = duration_log (me);
615 TODO: maybe property stroke-style should take different values,
616 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
620 SCM flag_style_scm = me->get_property ("flag-style");
621 if (scm_is_symbol (flag_style_scm))
622 flag_style = ly_symbol2string (flag_style_scm);
624 if (flag_style == "no-flag")
625 return Stencil ().smobbed_copy ();
629 string staffline_offs;
630 if (flag_style == "mensural")
631 /* Mensural notation: For notes on staff lines, use different
632 flags than for notes between staff lines. The idea is that
633 flags are always vertically aligned with the staff lines,
634 regardless if the note head is on a staff line or between two
635 staff lines. In other words, the inner end of a flag always
636 touches a staff line.
641 int p = (int) (rint (stem_end_position (me)));
643 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
646 staffline_offs = "2";
651 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
652 string font_char = flag_style
653 + to_string (dir) + staffline_offs + to_string (log);
654 Font_metric *fm = Font_interface::get_default_font (me);
655 Stencil flag = fm->find_by_name ("flags." + font_char);
656 if (flag.is_empty ())
657 me->warning (_f ("flag `%s' not found", font_char));
659 SCM stroke_style_scm = me->get_property ("stroke-style");
660 if (scm_is_string (stroke_style_scm))
662 string stroke_style = ly_scm2string (stroke_style_scm);
663 if (!stroke_style.empty ())
665 string font_char = flag_style + to_string (dir) + stroke_style;
666 Stencil stroke = fm->find_by_name ("flags." + font_char);
667 if (stroke.is_empty ())
669 font_char = to_string (dir) + stroke_style;
670 stroke = fm->find_by_name ("flags." + font_char);
672 if (stroke.is_empty ())
673 me->warning (_f ("flag stroke `%s' not found", font_char));
675 flag.add_stencil (stroke);
679 return flag.smobbed_copy ();
684 Stem::flag (Grob *me)
686 int log = duration_log (me);
688 || unsmob_grob (me->get_object ("beam")))
691 if (!is_normal_stem (me))
694 // This get_property call already evaluates the scheme function with
695 // the grob passed as argument! Thus, we only have to check if a valid
696 // stencil is returned.
697 SCM flag_style_scm = me->get_property ("flag");
698 if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
705 MAKE_SCHEME_CALLBACK (Stem, width, 1);
709 Grob *me = unsmob_grob (e);
713 if (is_invisible (me))
715 else if (unsmob_grob (me->get_object ("beam"))
716 || abs (duration_log (me)) <= 2)
718 r = Interval (-1, 1);
719 r *= thickness (me) / 2;
723 r = Interval (-1, 1) * thickness (me) * 0.5;
724 r.unite (flag (me).extent (X_AXIS));
726 return ly_interval2scm (r);
730 Stem::thickness (Grob *me)
732 return scm_to_double (me->get_property ("thickness"))
733 * Staff_symbol_referencer::line_thickness (me);
736 MAKE_SCHEME_CALLBACK (Stem, print, 1);
738 Stem::print (SCM smob)
740 Grob *me = unsmob_grob (smob);
741 Grob *beam = get_beam (me);
744 Direction d = get_grob_direction (me);
746 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
748 bool stemlet = stemlet_length > 0.0;
750 /* TODO: make the stem start a direction ?
751 This is required to avoid stems passing in tablature chords. */
753 = to_boolean (me->get_property ("avoid-note-head"))
760 if (!lh && stemlet && !beam)
763 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
766 if (is_invisible (me))
769 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
771 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
774 y2 = Staff_symbol_referencer::get_position (lh);
777 Real beam_translation = Beam::get_beam_translation (beam);
778 Real beam_thickness = Beam::get_beam_thickness (beam);
779 int beam_count = beam_multiplicity (me).length () + 1;
782 * (0.5 * beam_thickness
783 + beam_translation * max (0, (beam_count - 1))
784 + stemlet_length) / half_space;
787 Interval stem_y (min (y1, y2), max (y2, y1));
789 if (Grob *head = support_head (me))
792 must not take ledgers into account.
794 Interval head_height = head->extent (head, Y_AXIS);
795 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
797 y_attach = head_height.linear_combination (y_attach);
798 stem_y[Direction (-d)] += d * y_attach / half_space;
802 Real stem_width = thickness (me);
804 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
806 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
807 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
809 Stencil ss = Lookup::round_filled_box (b, blot);
810 mol.add_stencil (ss);
812 mol.add_stencil (get_translated_flag (me));
814 return mol.smobbed_copy ();
818 Stem::get_translated_flag (Grob *me)
820 Stencil fl = flag (me);
823 Direction d = get_grob_direction (me);
825 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
826 Real stem_width = thickness (me);
827 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
828 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
829 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
830 fl.translate_axis (stem_width / 2, X_AXIS);
837 move the stem to right of the notehead if it is up.
839 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
841 Stem::offset_callback (SCM smob)
843 Grob *me = unsmob_grob (smob);
845 extract_grob_set (me, "rests", rests);
848 Grob *rest = rests.back ();
849 Real r = rest->extent (rest, X_AXIS).center ();
850 return scm_from_double (r);
854 if (Grob *f = first_head (me))
856 Interval head_wid = f->extent (f, X_AXIS);
859 if (is_invisible (me))
862 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
864 Direction d = get_grob_direction (me);
865 Real real_attach = head_wid.linear_combination (d * attach);
866 Real r = real_attach;
868 /* If not centered: correct for stem thickness. */
871 Real rule_thick = thickness (me);
872 r += -d * rule_thick * 0.5;
874 return scm_from_double (r);
877 programming_error ("Weird stem.");
878 return scm_from_double (0.0);
882 Stem::get_beam (Grob *me)
884 SCM b = me->get_object ("beam");
885 return dynamic_cast<Spanner *> (unsmob_grob (b));
889 Stem::get_stem_info (Grob *me)
892 si.dir_ = get_grob_direction (me);
894 SCM scm_info = me->get_property ("stem-info");
895 si.ideal_y_ = scm_to_double (scm_car (scm_info));
896 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
900 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
902 Stem::calc_stem_info (SCM smob)
904 Grob *me = unsmob_grob (smob);
905 Direction my_dir = get_grob_direction (me);
909 programming_error ("no stem dir set");
913 Real staff_space = Staff_symbol_referencer::staff_space (me);
914 Grob *beam = get_beam (me);
918 (void) beam->get_property ("beaming");
921 Real beam_translation = Beam::get_beam_translation (beam);
922 Real beam_thickness = Beam::get_beam_thickness (beam);
923 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
925 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
927 /* Simple standard stem length */
928 SCM details = me->get_property ("details");
929 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
932 = (scm_is_pair (lengths)
933 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
937 stem only extends to center of beam
939 - 0.5 * beam_thickness)
942 /* Condition: sane minimum free stem length (chord to beams) */
943 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
946 Real ideal_minimum_free
947 = (scm_is_pair (lengths)
948 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
953 Real height_of_my_trem = 0.0;
954 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
958 = Stem_tremolo::vertical_length (trem)
959 /* hack a bit of space around the trem. */
965 It seems that also for ideal minimum length, we must use
966 the maximum beam count (for this direction):
968 \score { \relative c'' { a8[ a32] } }
970 must be horizontal. */
971 Real height_of_my_beams = beam_thickness
972 + (beam_count - 1) * beam_translation;
974 Real ideal_minimum_length = ideal_minimum_free
977 /* stem only extends to center of beam */
978 - 0.5 * beam_thickness;
980 ideal_length = max (ideal_length, ideal_minimum_length);
982 /* Convert to Y position, calculate for dir == UP */
984 = /* staff positions */
985 head_positions (me)[my_dir] * 0.5
986 * my_dir * staff_space;
987 Real ideal_y = note_start + ideal_length;
989 /* Conditions for Y position */
991 /* Lowest beam of (UP) beam must never be lower than second staffline
995 Although this (additional) rule is probably correct,
996 I expect that highest beam (UP) should also never be lower
997 than middle staffline, just as normal stems.
1001 Obviously not for grace beams.
1003 Also, not for knees. Seems to be a good thing. */
1004 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
1005 bool is_knee = to_boolean (beam->get_property ("knee"));
1006 if (!no_extend && !is_knee)
1008 /* Highest beam of (UP) beam must never be lower than middle
1010 ideal_y = max (ideal_y, 0.0);
1011 /* Lowest beam of (UP) beam must never be lower than second staffline */
1012 ideal_y = max (ideal_y, (-staff_space
1013 - beam_thickness + height_of_my_beams));
1016 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
1018 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
1022 = (scm_is_pair (bemfl)
1023 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1028 Real minimum_length = max (minimum_free, height_of_my_trem)
1029 + height_of_my_beams
1030 /* stem only extends to center of beam */
1031 - 0.5 * beam_thickness;
1034 Real minimum_y = note_start + minimum_length;
1035 Real shortest_y = minimum_y * my_dir;
1037 return scm_list_2 (scm_from_double (ideal_y),
1038 scm_from_double (shortest_y));
1042 Stem::beam_multiplicity (Grob *stem)
1044 SCM beaming = stem->get_property ("beaming");
1045 Slice le = int_list_to_slice (scm_car (beaming));
1046 Slice ri = int_list_to_slice (scm_cdr (beaming));
1052 Stem::is_cross_staff (Grob *stem)
1054 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1055 return beam && Beam::is_cross_staff (beam);
1058 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1060 Stem::calc_cross_staff (SCM smob)
1062 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1065 /* FIXME: Too many properties */
1066 ADD_INTERFACE (Stem,
1067 "The stem represents the graphical stem. In addition, it"
1068 " internally connects note heads, beams, and tremolos. Rests"
1069 " and whole notes have invisible stems.\n"
1071 "The following properties may be set in the @code{details}"
1075 "@item beamed-lengths\n"
1076 "List of stem lengths given beam multiplicity.\n"
1077 "@item beamed-minimum-free-lengths\n"
1078 "List of normal minimum free stem lengths (chord to beams)"
1079 " given beam multiplicity.\n"
1080 "@item beamed-extreme-minimum-free-lengths\n"
1081 "List of extreme minimum free stem lengths (chord to beams)"
1082 " given beam multiplicity.\n"
1084 "Default stem lengths. The list gives a length for each"
1086 "@item stem-shorten\n"
1087 "How much a stem in a forced direction should be shortened."
1088 " The list gives an amount depending on the number of flags"
1096 "beamlet-default-length "
1097 "beamlet-max-length-proportion "
1098 "default-direction "
1108 "neutral-direction "
1113 "stem-end-position "
1121 /****************************************************************/
1123 Stem_info::Stem_info ()
1125 ideal_y_ = shortest_y_ = 0;
1130 Stem_info::scale (Real x)