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, calc_stem_begin_position, 1);
738 Stem::calc_stem_begin_position (SCM smob)
740 Grob *me = unsmob_grob (smob);
741 Direction d = get_grob_direction (me);
742 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
744 = to_boolean (me->get_property ("avoid-note-head"))
748 Real pos = Staff_symbol_referencer::get_position (lh);
750 if (Grob *head = support_head (me))
752 Interval head_height = head->extent (head, Y_AXIS);
753 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
755 y_attach = head_height.linear_combination (y_attach);
756 pos += d * y_attach / half_space;
759 return scm_from_double (pos);
762 MAKE_SCHEME_CALLBACK (Stem, print, 1);
764 Stem::print (SCM smob)
766 Grob *me = unsmob_grob (smob);
767 Grob *beam = get_beam (me);
770 Direction d = get_grob_direction (me);
772 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
774 bool stemlet = stemlet_length > 0.0;
776 /* TODO: make the stem start a direction ?
777 This is required to avoid stems passing in tablature chords. */
779 = to_boolean (me->get_property ("avoid-note-head"))
786 if (!lh && stemlet && !beam)
789 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
792 if (is_invisible (me))
795 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
797 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
800 y2 = robust_scm2double (me->get_property ("stem-begin-position"), 0.0);
803 Real beam_translation = Beam::get_beam_translation (beam);
804 Real beam_thickness = Beam::get_beam_thickness (beam);
805 int beam_count = beam_multiplicity (me).length () + 1;
808 * (0.5 * beam_thickness
809 + beam_translation * max (0, (beam_count - 1))
810 + stemlet_length) / half_space;
813 Interval stem_y (min (y1, y2), max (y2, y1));
816 Real stem_width = thickness (me);
818 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
820 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
821 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
823 Stencil ss = Lookup::round_filled_box (b, blot);
824 mol.add_stencil (ss);
826 mol.add_stencil (get_translated_flag (me));
828 return mol.smobbed_copy ();
832 Stem::get_translated_flag (Grob *me)
834 Stencil fl = flag (me);
837 Direction d = get_grob_direction (me);
839 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
840 Real stem_width = thickness (me);
841 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
842 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
843 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
844 fl.translate_axis (stem_width / 2, X_AXIS);
851 move the stem to right of the notehead if it is up.
853 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
855 Stem::offset_callback (SCM smob)
857 Grob *me = unsmob_grob (smob);
859 extract_grob_set (me, "rests", rests);
862 Grob *rest = rests.back ();
863 Real r = rest->extent (rest, X_AXIS).center ();
864 return scm_from_double (r);
868 if (Grob *f = first_head (me))
870 Interval head_wid = f->extent (f, X_AXIS);
873 if (is_invisible (me))
876 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
878 Direction d = get_grob_direction (me);
879 Real real_attach = head_wid.linear_combination (d * attach);
880 Real r = real_attach;
882 /* If not centered: correct for stem thickness. */
885 Real rule_thick = thickness (me);
886 r += -d * rule_thick * 0.5;
888 return scm_from_double (r);
891 programming_error ("Weird stem.");
892 return scm_from_double (0.0);
896 Stem::get_beam (Grob *me)
898 SCM b = me->get_object ("beam");
899 return dynamic_cast<Spanner *> (unsmob_grob (b));
903 Stem::get_stem_info (Grob *me)
906 si.dir_ = get_grob_direction (me);
908 SCM scm_info = me->get_property ("stem-info");
909 si.ideal_y_ = scm_to_double (scm_car (scm_info));
910 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
914 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
916 Stem::calc_stem_info (SCM smob)
918 Grob *me = unsmob_grob (smob);
919 Direction my_dir = get_grob_direction (me);
923 programming_error ("no stem dir set");
927 Real staff_space = Staff_symbol_referencer::staff_space (me);
928 Grob *beam = get_beam (me);
932 (void) beam->get_property ("beaming");
935 Real beam_translation = Beam::get_beam_translation (beam);
936 Real beam_thickness = Beam::get_beam_thickness (beam);
937 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
939 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
941 /* Simple standard stem length */
942 SCM details = me->get_property ("details");
943 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
946 = (scm_is_pair (lengths)
947 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
951 stem only extends to center of beam
953 - 0.5 * beam_thickness)
956 /* Condition: sane minimum free stem length (chord to beams) */
957 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
960 Real ideal_minimum_free
961 = (scm_is_pair (lengths)
962 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
967 Real height_of_my_trem = 0.0;
968 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
972 = Stem_tremolo::vertical_length (trem)
973 /* hack a bit of space around the trem. */
979 It seems that also for ideal minimum length, we must use
980 the maximum beam count (for this direction):
982 \score { \relative c'' { a8[ a32] } }
984 must be horizontal. */
985 Real height_of_my_beams = beam_thickness
986 + (beam_count - 1) * beam_translation;
988 Real ideal_minimum_length = ideal_minimum_free
991 /* stem only extends to center of beam */
992 - 0.5 * beam_thickness;
994 ideal_length = max (ideal_length, ideal_minimum_length);
996 /* Convert to Y position, calculate for dir == UP */
998 = /* staff positions */
999 head_positions (me)[my_dir] * 0.5
1000 * my_dir * staff_space;
1001 Real ideal_y = note_start + ideal_length;
1003 /* Conditions for Y position */
1005 /* Lowest beam of (UP) beam must never be lower than second staffline
1009 Although this (additional) rule is probably correct,
1010 I expect that highest beam (UP) should also never be lower
1011 than middle staffline, just as normal stems.
1015 Obviously not for grace beams.
1017 Also, not for knees. Seems to be a good thing. */
1018 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
1019 bool is_knee = to_boolean (beam->get_property ("knee"));
1020 if (!no_extend && !is_knee)
1022 /* Highest beam of (UP) beam must never be lower than middle
1024 ideal_y = max (ideal_y, 0.0);
1025 /* Lowest beam of (UP) beam must never be lower than second staffline */
1026 ideal_y = max (ideal_y, (-staff_space
1027 - beam_thickness + height_of_my_beams));
1030 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
1032 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
1036 = (scm_is_pair (bemfl)
1037 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1042 Real minimum_length = max (minimum_free, height_of_my_trem)
1043 + height_of_my_beams
1044 /* stem only extends to center of beam */
1045 - 0.5 * beam_thickness;
1048 Real minimum_y = note_start + minimum_length;
1049 Real shortest_y = minimum_y * my_dir;
1051 return scm_list_2 (scm_from_double (ideal_y),
1052 scm_from_double (shortest_y));
1056 Stem::beam_multiplicity (Grob *stem)
1058 SCM beaming = stem->get_property ("beaming");
1059 Slice le = int_list_to_slice (scm_car (beaming));
1060 Slice ri = int_list_to_slice (scm_cdr (beaming));
1066 Stem::is_cross_staff (Grob *stem)
1068 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1069 return beam && Beam::is_cross_staff (beam);
1072 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1074 Stem::calc_cross_staff (SCM smob)
1076 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1079 /* FIXME: Too many properties */
1080 ADD_INTERFACE (Stem,
1081 "The stem represents the graphical stem. In addition, it"
1082 " internally connects note heads, beams, and tremolos. Rests"
1083 " and whole notes have invisible stems.\n"
1085 "The following properties may be set in the @code{details}"
1089 "@item beamed-lengths\n"
1090 "List of stem lengths given beam multiplicity.\n"
1091 "@item beamed-minimum-free-lengths\n"
1092 "List of normal minimum free stem lengths (chord to beams)"
1093 " given beam multiplicity.\n"
1094 "@item beamed-extreme-minimum-free-lengths\n"
1095 "List of extreme minimum free stem lengths (chord to beams)"
1096 " given beam multiplicity.\n"
1098 "Default stem lengths. The list gives a length for each"
1100 "@item stem-shorten\n"
1101 "How much a stem in a forced direction should be shortened."
1102 " The list gives an amount depending on the number of flags"
1110 "beamlet-default-length "
1111 "beamlet-max-length-proportion "
1112 "default-direction "
1122 "neutral-direction "
1127 "stem-begin-position "
1128 "stem-end-position "
1136 /****************************************************************/
1138 Stem_info::Stem_info ()
1140 ideal_y_ = shortest_y_ = 0;
1145 Stem_info::scale (Real x)