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)
108 Stem::set_stemend (Grob *me, Real se)
111 Direction d = get_grob_direction (me);
113 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
114 me->warning (_ ("weird stem size, check for narrow beams"));
116 me->set_property ("stem-end-position", scm_from_double (se));
119 /* Note head that determines hshift for upstems
120 WARNING: triggers direction */
122 Stem::support_head (Grob *me)
124 extract_grob_set (me, "note-heads", heads);
125 if (heads.size () == 1)
128 return first_head (me);
132 Stem::head_count (Grob *me)
134 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
137 /* The note head which forms one end of the stem.
138 WARNING: triggers direction */
140 Stem::first_head (Grob *me)
142 Direction d = get_grob_direction (me);
144 return extremal_heads (me)[-d];
148 /* The note head opposite to the first head. */
150 Stem::last_head (Grob *me)
152 Direction d = get_grob_direction (me);
154 return extremal_heads (me)[d];
159 START is part where stem reaches `last' head.
161 This function returns a drul with (bottom-head, top-head).
164 Stem::extremal_heads (Grob *me)
166 const int inf = INT_MAX;
167 Drul_array<int> extpos;
171 Drul_array<Grob *> exthead (0, 0);
172 extract_grob_set (me, "note-heads", heads);
174 for (vsize i = heads.size (); i--;)
177 int p = Staff_symbol_referencer::get_rounded_position (n);
182 if (d * p > d * extpos[d])
188 while (flip (&d) != DOWN);
193 /* The positions, in ascending order. */
195 Stem::note_head_positions (Grob *me)
198 extract_grob_set (me, "note-heads", heads);
200 for (vsize i = heads.size (); i--;)
203 int p = Staff_symbol_referencer::get_rounded_position (n);
208 vector_sort (ps, less<int> ());
213 Stem::add_head (Grob *me, Grob *n)
215 n->set_object ("stem", me->self_scm ());
217 if (Note_head::has_interface (n))
218 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
219 else if (Rest::has_interface (n))
220 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
224 Stem::is_invisible (Grob *me)
226 return !is_normal_stem (me)
227 && (robust_scm2double (me->get_property ("stemlet-length"),
232 Stem::is_normal_stem (Grob *me)
234 return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
237 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
239 Stem::pure_height (SCM smob,
243 Grob *me = unsmob_grob (smob);
246 if (!is_normal_stem (me))
247 return ly_interval2scm (iv);
249 Real ss = Staff_symbol_referencer::staff_space (me);
250 Real rad = Staff_symbol_referencer::staff_radius (me);
252 if (!to_boolean (me->get_property ("cross-staff")))
254 Real len_in_halfspaces;
255 SCM user_set_len_scm = me->get_property_data ("length");
256 if (scm_is_number (user_set_len_scm))
257 len_in_halfspaces = scm_to_double (user_set_len_scm);
259 len_in_halfspaces = scm_to_double (calc_length (smob));
260 Real len = len_in_halfspaces * ss / 2;
261 Direction dir = get_grob_direction (me);
263 Interval hp = head_positions (me);
265 iv = Interval (0, len);
267 iv = Interval (-len, 0);
271 iv.translate (hp[dir] * ss / 2);
272 iv.add_point (hp[-dir] * ss / 2);
275 /* extend the stem (away from the head) to cover the staff */
277 iv[UP] = max (iv[UP], rad * ss);
279 iv[DOWN] = min (iv[DOWN], -rad * ss);
282 iv = Interval (-rad * ss, rad * ss);
284 return ly_interval2scm (iv);
287 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
289 Stem::calc_stem_end_position (SCM smob)
291 Grob *me = unsmob_grob (smob);
293 if (!head_count (me))
294 return scm_from_double (0.0);
296 if (Grob *beam = get_beam (me))
298 (void) beam->get_property ("quantized-positions");
299 return me->get_property ("stem-end-position");
304 /* WARNING: IN HALF SPACES */
305 Real length = robust_scm2double (me->get_property ("length"), 7);
307 Direction dir = get_grob_direction (me);
308 Interval hp = head_positions (me);
309 Real stem_end = dir ? hp[dir] + dir * length : 0;
311 /* TODO: change name to extend-stems to staff/center/'() */
312 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
313 if (!no_extend && dir * stem_end < 0)
316 return scm_from_double (stem_end);
319 /* Length is in half-spaces (or: positions) here. */
320 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
322 Stem::calc_length (SCM smob)
324 Grob *me = unsmob_grob (smob);
326 SCM details = me->get_property ("details");
327 int durlog = duration_log (me);
329 Real ss = Staff_symbol_referencer::staff_space (me);
330 Real staff_rad = Staff_symbol_referencer::staff_radius (me);
332 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
334 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
336 Direction dir = get_grob_direction (me);
338 /* Stems in unnatural (forced) direction should be shortened,
339 according to [Roush & Gourlay] */
340 Interval hp = head_positions (me);
341 if (dir && dir * hp[dir] >= 0)
343 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
344 SCM scm_shorten = scm_is_pair (sshorten)
345 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
346 Real shorten_property = 2 * robust_scm2double (scm_shorten, 0);
347 /* change in length between full-size and shortened stems is executed gradually.
348 "transition area" = stems between full-sized and fully-shortened.
350 Real quarter_stem_length = 2 * scm_to_double (robust_list_ref (0, s));
351 /* shortening_step = difference in length between consecutive stem lengths
352 in transition area. The bigger the difference between full-sized
353 and shortened stems, the bigger shortening_step is.
354 (but not greater than 1/2 and not smaller than 1/4).
355 value 6 is heuristic; it determines the suggested transition slope steepnesas.
357 Real shortening_step = min (max (0.25, (shorten_property / 6)), 0.5);
358 /* Shortening of unflagged stems should begin on the first stem that sticks
359 more than 1 staffspace (2 units) out of the staff.
360 Shortening of flagged stems begins in the same moment as unflagged ones,
361 but not earlier than on the middle line note.
363 Real which_step = (min (1.0, quarter_stem_length - (2 * staff_rad) - 2.0)) + abs (hp[dir]);
364 Real shorten = min (max (0.0, (shortening_step * which_step)), shorten_property);
369 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
372 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
373 if (t_flag && !unsmob_grob (me->get_object ("beam")))
375 /* Crude hack: add extra space if tremolo flag is there.
377 We can't do this for the beam, since we get into a loop
378 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
381 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
383 /* We don't want to add the whole extent of the flag because the trem
384 and the flag can overlap partly. beam_translation gives a good
388 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
389 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
390 minlen += 2 * (durlog - 1.5) * beam_trans;
392 /* up-stems need even a little more space to avoid collisions. This
393 needs to be in sync with the tremolo positioning code in
394 Stem_tremolo::print */
396 minlen += beam_trans;
398 length = max (length, minlen + 1.0);
401 return scm_from_double (length);
403 /* The log of the duration (Number of hooks on the flag minus two) */
405 Stem::duration_log (Grob *me)
407 SCM s = me->get_property ("duration-log");
408 return (scm_is_number (s)) ? scm_to_int (s) : 2;
411 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
413 Stem::calc_positioning_done (SCM smob)
415 Grob *me = unsmob_grob (smob);
416 if (!head_count (me))
419 me->set_property ("positioning-done", SCM_BOOL_T);
421 extract_grob_set (me, "note-heads", ro_heads);
422 vector<Grob *> heads (ro_heads);
423 vector_sort (heads, position_less);
424 Direction dir = get_grob_direction (me);
429 Real thick = thickness (me);
431 Grob *hed = support_head (me);
434 programming_error ("Stem dir must be up or down.");
436 set_grob_direction (me, dir);
439 bool is_harmonic_centered = false;
440 for (vsize i = 0; i < heads.size (); i++)
441 is_harmonic_centered = is_harmonic_centered
442 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
443 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
445 Real w = hed->extent (hed, X_AXIS)[dir];
446 for (vsize i = 0; i < heads.size (); i++)
448 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
450 if (is_harmonic_centered)
452 = hed->extent (hed, X_AXIS).linear_combination (CENTER)
453 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
455 heads[i]->translate_axis (amount, X_AXIS);
458 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
459 for (vsize i = 1; i < heads.size (); i++)
461 Real p = Staff_symbol_referencer::get_position (heads[i]);
462 Real dy = fabs (lastpos - p);
465 dy should always be 0.5, 0.0, 1.0, but provide safety margin
472 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
474 Direction d = get_grob_direction (me);
476 Reversed head should be shifted ell-thickness, but this
477 looks too crowded, so we only shift ell-0.5*thickness.
479 This leads to assymetry: Normal heads overlap the
480 stem 100% whereas reversed heads only overlaps the
484 Real reverse_overlap = 0.5;
485 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
488 if (is_invisible (me))
489 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
494 For some cases we should kern some more: when the
495 distance between the next or prev note is too large, we'd
496 get large white gaps, eg.
517 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
519 Stem::calc_direction (SCM smob)
521 Grob *me = unsmob_grob (smob);
522 Direction dir = CENTER;
523 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
525 SCM ignore_me = beam->get_property ("direction");
527 dir = get_grob_direction (me);
531 SCM dd = me->get_property ("default-direction");
534 return me->get_property ("neutral-direction");
537 return scm_from_int (dir);
540 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
542 Stem::calc_default_direction (SCM smob)
544 Grob *me = unsmob_grob (smob);
546 Direction dir = CENTER;
547 int staff_center = 0;
548 Interval hp = head_positions (me);
551 int udistance = (int) (UP * hp[UP] - staff_center);
552 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
554 dir = Direction (sign (ddistance - udistance));
557 return scm_from_int (dir);
560 MAKE_SCHEME_CALLBACK (Stem, height, 1);
562 Stem::height (SCM smob)
564 Grob *me = unsmob_grob (smob);
565 if (!is_normal_stem (me))
566 return ly_interval2scm (Interval ());
568 Direction dir = get_grob_direction (me);
570 Grob *beam = get_beam (me);
573 /* trigger set-stem-lengths. */
574 beam->get_property ("quantized-positions");
578 Can't get_stencil (), since that would cache stencils too early.
579 This causes problems with beams.
581 Stencil *stencil = unsmob_stencil (print (smob));
582 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
587 programming_error ("no stem direction");
590 iv[dir] += dir * Beam::get_beam_thickness (beam) * 0.5;
593 return ly_interval2scm (iv);
597 Stem::stem_end_position (Grob *me)
599 return robust_scm2double (me->get_property ("stem-end-position"), 0);
602 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
604 Stem::calc_flag (SCM smob)
606 Grob *me = unsmob_grob (smob);
608 int log = duration_log (me);
610 TODO: maybe property stroke-style should take different values,
611 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
615 SCM flag_style_scm = me->get_property ("flag-style");
616 if (scm_is_symbol (flag_style_scm))
617 flag_style = ly_symbol2string (flag_style_scm);
619 if (flag_style == "no-flag")
620 return Stencil ().smobbed_copy ();
624 string staffline_offs;
625 if (flag_style == "mensural")
626 /* Mensural notation: For notes on staff lines, use different
627 flags than for notes between staff lines. The idea is that
628 flags are always vertically aligned with the staff lines,
629 regardless if the note head is on a staff line or between two
630 staff lines. In other words, the inner end of a flag always
631 touches a staff line.
636 int p = (int) (rint (stem_end_position (me)));
638 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
641 staffline_offs = "2";
646 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
647 string font_char = flag_style
648 + to_string (dir) + staffline_offs + to_string (log);
649 Font_metric *fm = Font_interface::get_default_font (me);
650 Stencil flag = fm->find_by_name ("flags." + font_char);
651 if (flag.is_empty ())
652 me->warning (_f ("flag `%s' not found", font_char));
654 SCM stroke_style_scm = me->get_property ("stroke-style");
655 if (scm_is_string (stroke_style_scm))
657 string stroke_style = ly_scm2string (stroke_style_scm);
658 if (!stroke_style.empty ())
660 string font_char = flag_style + to_string (dir) + stroke_style;
661 Stencil stroke = fm->find_by_name ("flags." + font_char);
662 if (stroke.is_empty ())
664 font_char = to_string (dir) + stroke_style;
665 stroke = fm->find_by_name ("flags." + font_char);
667 if (stroke.is_empty ())
668 me->warning (_f ("flag stroke `%s' not found", font_char));
670 flag.add_stencil (stroke);
674 return flag.smobbed_copy ();
678 Stem::flag (Grob *me)
680 int log = duration_log (me);
682 || unsmob_grob (me->get_object ("beam")))
685 if (!is_normal_stem (me))
688 // This get_property call already evaluates the scheme function with
689 // the grob passed as argument! Thus, we only have to check if a valid
690 // stencil is returned.
691 SCM flag_style_scm = me->get_property ("flag");
692 if (Stencil *flag = unsmob_stencil (flag_style_scm))
702 MAKE_SCHEME_CALLBACK (Stem, width, 1);
706 Grob *me = unsmob_grob (e);
710 if (is_invisible (me))
712 else if (unsmob_grob (me->get_object ("beam"))
713 || abs (duration_log (me)) <= 2)
715 r = Interval (-1, 1);
716 r *= thickness (me) / 2;
720 r = Interval (-1, 1) * thickness (me) * 0.5;
721 r.unite (flag (me).extent (X_AXIS));
723 return ly_interval2scm (r);
727 Stem::thickness (Grob *me)
729 return scm_to_double (me->get_property ("thickness"))
730 * Staff_symbol_referencer::line_thickness (me);
733 MAKE_SCHEME_CALLBACK (Stem, calc_stem_begin_position, 1);
735 Stem::calc_stem_begin_position (SCM smob)
737 Grob *me = unsmob_grob (smob);
738 Direction d = get_grob_direction (me);
739 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
741 = to_boolean (me->get_property ("avoid-note-head"))
745 Real pos = Staff_symbol_referencer::get_position (lh);
747 if (Grob *head = support_head (me))
749 Interval head_height = head->extent (head, Y_AXIS);
750 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
752 y_attach = head_height.linear_combination (y_attach);
753 pos += d * y_attach / half_space;
756 return scm_from_double (pos);
759 MAKE_SCHEME_CALLBACK (Stem, print, 1);
761 Stem::print (SCM smob)
763 Grob *me = unsmob_grob (smob);
764 Grob *beam = get_beam (me);
767 Direction d = get_grob_direction (me);
769 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
771 bool stemlet = stemlet_length > 0.0;
773 /* TODO: make the stem start a direction ?
774 This is required to avoid stems passing in tablature chords. */
776 = to_boolean (me->get_property ("avoid-note-head"))
783 if (!lh && stemlet && !beam)
786 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
789 if (is_invisible (me))
792 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
794 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
797 y2 = robust_scm2double (me->get_property ("stem-begin-position"), 0.0);
800 Real beam_translation = Beam::get_beam_translation (beam);
801 Real beam_thickness = Beam::get_beam_thickness (beam);
802 int beam_count = beam_multiplicity (me).length () + 1;
805 * (0.5 * beam_thickness
806 + beam_translation * max (0, (beam_count - 1))
807 + stemlet_length) / half_space;
810 Interval stem_y (min (y1, y2), max (y2, y1));
813 Real stem_width = thickness (me);
815 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
817 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
818 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
820 Stencil ss = Lookup::round_filled_box (b, blot);
821 mol.add_stencil (ss);
823 mol.add_stencil (get_translated_flag (me));
825 return mol.smobbed_copy ();
829 Stem::get_translated_flag (Grob *me)
831 Stencil fl = flag (me);
834 Direction d = get_grob_direction (me);
836 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
837 Real stem_width = thickness (me);
838 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
839 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
840 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
841 fl.translate_axis (stem_width / 2, X_AXIS);
847 move the stem to right of the notehead if it is up.
849 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
851 Stem::offset_callback (SCM smob)
853 Grob *me = unsmob_grob (smob);
855 extract_grob_set (me, "rests", rests);
858 Grob *rest = rests.back ();
859 Real r = rest->extent (rest, X_AXIS).center ();
860 return scm_from_double (r);
863 if (Grob *f = first_head (me))
865 Interval head_wid = f->extent (f, X_AXIS);
868 if (is_invisible (me))
871 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
873 Direction d = get_grob_direction (me);
874 Real real_attach = head_wid.linear_combination (d * attach);
875 Real r = real_attach;
877 /* If not centered: correct for stem thickness. */
880 Real rule_thick = thickness (me);
881 r += -d * rule_thick * 0.5;
883 return scm_from_double (r);
886 programming_error ("Weird stem.");
887 return scm_from_double (0.0);
891 Stem::get_beam (Grob *me)
893 SCM b = me->get_object ("beam");
894 return dynamic_cast<Spanner *> (unsmob_grob (b));
898 Stem::get_stem_info (Grob *me)
901 si.dir_ = get_grob_direction (me);
903 SCM scm_info = me->get_property ("stem-info");
904 si.ideal_y_ = scm_to_double (scm_car (scm_info));
905 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
909 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
911 Stem::calc_stem_info (SCM smob)
913 Grob *me = unsmob_grob (smob);
914 Direction my_dir = get_grob_direction (me);
918 programming_error ("no stem dir set");
922 Real staff_space = Staff_symbol_referencer::staff_space (me);
923 Grob *beam = get_beam (me);
927 (void) beam->get_property ("beaming");
930 Real beam_translation = Beam::get_beam_translation (beam);
931 Real beam_thickness = Beam::get_beam_thickness (beam);
932 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
934 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
936 /* Simple standard stem length */
937 SCM details = me->get_property ("details");
938 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
941 = (scm_is_pair (lengths)
942 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
946 stem only extends to center of beam
948 - 0.5 * beam_thickness)
951 /* Condition: sane minimum free stem length (chord to beams) */
952 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
955 Real ideal_minimum_free
956 = (scm_is_pair (lengths)
957 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
962 Real height_of_my_trem = 0.0;
963 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
967 = Stem_tremolo::vertical_length (trem)
968 /* hack a bit of space around the trem. */
973 It seems that also for ideal minimum length, we must use
974 the maximum beam count (for this direction):
976 \score { \relative c'' { a8[ a32] } }
978 must be horizontal. */
979 Real height_of_my_beams = beam_thickness
980 + (beam_count - 1) * beam_translation;
982 Real ideal_minimum_length = ideal_minimum_free
985 /* stem only extends to center of beam */
986 - 0.5 * beam_thickness;
988 ideal_length = max (ideal_length, ideal_minimum_length);
990 /* Convert to Y position, calculate for dir == UP */
992 = /* staff positions */
993 head_positions (me)[my_dir] * 0.5
994 * my_dir * staff_space;
995 Real ideal_y = note_start + ideal_length;
997 /* Conditions for Y position */
999 /* Lowest beam of (UP) beam must never be lower than second staffline
1003 Although this (additional) rule is probably correct,
1004 I expect that highest beam (UP) should also never be lower
1005 than middle staffline, just as normal stems.
1009 Obviously not for grace beams.
1011 Also, not for knees. Seems to be a good thing. */
1012 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
1013 bool is_knee = to_boolean (beam->get_property ("knee"));
1014 if (!no_extend && !is_knee)
1016 /* Highest beam of (UP) beam must never be lower than middle
1018 ideal_y = max (ideal_y, 0.0);
1019 /* Lowest beam of (UP) beam must never be lower than second staffline */
1020 ideal_y = max (ideal_y, (-staff_space
1021 - beam_thickness + height_of_my_beams));
1024 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
1026 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
1030 = (scm_is_pair (bemfl)
1031 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1036 Real minimum_length = max (minimum_free, height_of_my_trem)
1037 + height_of_my_beams
1038 /* stem only extends to center of beam */
1039 - 0.5 * beam_thickness;
1042 Real minimum_y = note_start + minimum_length;
1043 Real shortest_y = minimum_y * my_dir;
1045 return scm_list_2 (scm_from_double (ideal_y),
1046 scm_from_double (shortest_y));
1050 Stem::beam_multiplicity (Grob *stem)
1052 SCM beaming = stem->get_property ("beaming");
1053 Slice le = int_list_to_slice (scm_car (beaming));
1054 Slice ri = int_list_to_slice (scm_cdr (beaming));
1060 Stem::is_cross_staff (Grob *stem)
1062 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1063 return beam && Beam::is_cross_staff (beam);
1066 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1068 Stem::calc_cross_staff (SCM smob)
1070 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1073 /* FIXME: Too many properties */
1074 ADD_INTERFACE (Stem,
1075 "The stem represents the graphical stem. In addition, it"
1076 " internally connects note heads, beams, and tremolos. Rests"
1077 " and whole notes have invisible stems.\n"
1079 "The following properties may be set in the @code{details}"
1083 "@item beamed-lengths\n"
1084 "List of stem lengths given beam multiplicity.\n"
1085 "@item beamed-minimum-free-lengths\n"
1086 "List of normal minimum free stem lengths (chord to beams)"
1087 " given beam multiplicity.\n"
1088 "@item beamed-extreme-minimum-free-lengths\n"
1089 "List of extreme minimum free stem lengths (chord to beams)"
1090 " given beam multiplicity.\n"
1092 "Default stem lengths. The list gives a length for each"
1094 "@item stem-shorten\n"
1095 "How much a stem in a forced direction should be shortened."
1096 " The list gives an amount depending on the number of flags"
1104 "beamlet-default-length "
1105 "beamlet-max-length-proportion "
1106 "default-direction "
1116 "neutral-direction "
1121 "stem-begin-position "
1122 "stem-end-position "
1130 /****************************************************************/
1132 Stem_info::Stem_info ()
1134 ideal_y_ = shortest_y_ = 0;
1139 Stem_info::scale (Real x)