Consider accidentals in optical spacing correction.
[lilypond.git] / lily / stem.cc
blob8ba6f2b6c9cdba33022481ba5b0113d4abdecfbe
1 /*
2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
9 TODO: This is way too hairy
11 TODO: fix naming.
13 Stem-end, chord-start, etc. is all confusing naming.
16 #include "stem.hh"
17 #include "spanner.hh"
19 #include <cmath> // rint
20 using namespace std;
22 #include "beam.hh"
23 #include "directional-element-interface.hh"
24 #include "dot-column.hh"
25 #include "font-interface.hh"
26 #include "international.hh"
27 #include "lookup.hh"
28 #include "misc.hh"
29 #include "note-head.hh"
30 #include "output-def.hh"
31 #include "paper-column.hh"
32 #include "pointer-group-interface.hh"
33 #include "rest.hh"
34 #include "rhythmic-head.hh"
35 #include "side-position-interface.hh"
36 #include "staff-symbol-referencer.hh"
37 #include "stem-tremolo.hh"
38 #include "warn.hh"
40 void
41 Stem::set_beaming (Grob *me, int beam_count, Direction d)
43 SCM pair = me->get_property ("beaming");
45 if (!scm_is_pair (pair))
47 pair = scm_cons (SCM_EOL, SCM_EOL);
48 me->set_property ("beaming", pair);
51 SCM lst = index_get_cell (pair, d);
52 if (beam_count)
53 for (int i = 0; i < beam_count; i++)
54 lst = scm_cons (scm_from_int (i), lst);
55 else
56 lst = SCM_BOOL_F;
58 index_set_cell (pair, d, lst);
61 int
62 Stem::get_beaming (Grob *me, Direction d)
64 SCM pair = me->get_property ("beaming");
65 if (!scm_is_pair (pair))
66 return 0;
68 SCM lst = index_get_cell (pair, d);
70 int len = scm_ilength (lst);
71 return max (len, 0);
74 Interval
75 Stem::head_positions (Grob *me)
77 if (head_count (me))
79 Drul_array<Grob *> e (extremal_heads (me));
80 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
81 Staff_symbol_referencer::get_position (e[UP]));
83 return Interval ();
86 Real
87 Stem::chord_start_y (Grob *me)
89 Interval hp = head_positions (me);
90 if (!hp.is_empty ())
91 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
92 * 0.5;
93 return 0;
98 void
99 Stem::set_stemend (Grob *me, Real se)
101 // todo: margins
102 Direction d = get_grob_direction (me);
104 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
105 me->warning (_ ("weird stem size, check for narrow beams"));
107 me->set_property ("stem-end-position", scm_from_double (se));
110 /* Note head that determines hshift for upstems
111 WARNING: triggers direction */
112 Grob *
113 Stem::support_head (Grob *me)
115 extract_grob_set (me, "note-heads", heads);
116 if (heads.size () == 1)
117 return heads[0];
119 return first_head (me);
123 Stem::head_count (Grob *me)
125 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
128 /* The note head which forms one end of the stem.
129 WARNING: triggers direction */
130 Grob *
131 Stem::first_head (Grob *me)
133 Direction d = get_grob_direction (me);
134 if (d)
135 return extremal_heads (me)[-d];
136 return 0;
139 /* The note head opposite to the first head. */
140 Grob *
141 Stem::last_head (Grob *me)
143 Direction d = get_grob_direction (me);
144 if (d)
145 return extremal_heads (me)[d];
146 return 0;
150 START is part where stem reaches `last' head.
152 This function returns a drul with (bottom-head, top-head).
154 Drul_array<Grob *>
155 Stem::extremal_heads (Grob *me)
157 const int inf = INT_MAX;
158 Drul_array<int> extpos;
159 extpos[DOWN] = inf;
160 extpos[UP] = -inf;
162 Drul_array<Grob *> exthead (0, 0);
163 extract_grob_set (me, "note-heads", heads);
165 for (vsize i = heads.size (); i--;)
167 Grob *n = heads[i];
168 int p = Staff_symbol_referencer::get_rounded_position (n);
170 Direction d = LEFT;
173 if (d * p > d * extpos[d])
175 exthead[d] = n;
176 extpos[d] = p;
179 while (flip (&d) != DOWN);
181 return exthead;
184 /* The positions, in ascending order. */
185 vector<int>
186 Stem::note_head_positions (Grob *me)
188 vector<int> ps;
189 extract_grob_set (me, "note-heads", heads);
191 for (vsize i = heads.size (); i--;)
193 Grob *n = heads[i];
194 int p = Staff_symbol_referencer::get_rounded_position (n);
196 ps.push_back (p);
199 vector_sort (ps, less<int> ());
200 return ps;
203 void
204 Stem::add_head (Grob *me, Grob *n)
206 n->set_object ("stem", me->self_scm ());
208 if (Note_head::has_interface (n))
209 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
210 else if (Rest::has_interface (n))
211 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
214 bool
215 Stem::is_invisible (Grob *me)
217 return !is_normal_stem (me)
218 && (robust_scm2double (me->get_property ("stemlet-length"),
219 0.0) == 0.0);
223 bool
224 Stem::is_normal_stem (Grob *me)
226 return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
230 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
232 Stem::pure_height (SCM smob,
233 SCM /* start */,
234 SCM /* end */)
236 Grob *me = unsmob_grob (smob);
237 Interval iv;
239 if (!is_normal_stem (me))
240 return ly_interval2scm (iv);
242 Real ss = Staff_symbol_referencer::staff_space (me);
243 Real rad = Staff_symbol_referencer::staff_radius (me);
245 if (!to_boolean (me->get_property ("cross-staff")))
247 Real len = scm_to_double (calc_length (smob)) * ss / 2;
248 Direction dir = get_grob_direction (me);
250 Interval hp = head_positions (me);
251 if (dir == UP)
252 iv = Interval (0, len);
253 else
254 iv = Interval (-len, 0);
256 if (!hp.is_empty ())
257 iv.translate (hp[dir] * ss / 2);
259 /* extend the stem (away from the head) to cover the staff */
260 if (dir == UP)
261 iv[UP] = max (iv[UP], rad * ss);
262 else
263 iv[DOWN] = min (iv[DOWN], -rad * ss);
265 else
266 iv = Interval (-rad * ss, rad * ss);
268 return ly_interval2scm (iv);
271 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
273 Stem::calc_stem_end_position (SCM smob)
275 Grob *me = unsmob_grob (smob);
277 if (!head_count (me))
278 return scm_from_double (0.0);
280 if (Grob *beam = get_beam (me))
282 (void) beam->get_property ("quantized-positions");
283 return me->get_property ("stem-end-position");
286 vector<Real> a;
288 /* WARNING: IN HALF SPACES */
289 Real length = robust_scm2double (me->get_property ("length"), 7);
291 Direction dir = get_grob_direction (me);
292 Interval hp = head_positions (me);
293 Real stem_end = dir ? hp[dir] + dir * length : 0;
295 /* TODO: change name to extend-stems to staff/center/'() */
296 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
297 if (!no_extend && dir * stem_end < 0)
298 stem_end = 0.0;
300 return scm_from_double (stem_end);
303 /* Length is in half-spaces (or: positions) here. */
304 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
306 Stem::calc_length (SCM smob)
308 Grob *me = unsmob_grob (smob);
310 SCM details = me->get_property ("details");
311 int durlog = duration_log (me);
313 Real ss = Staff_symbol_referencer::staff_space (me);
314 Real length = 7;
315 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
316 if (scm_is_pair (s))
317 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
319 Direction dir = get_grob_direction (me);
321 /* Stems in unnatural (forced) direction should be shortened,
322 according to [Roush & Gourlay] */
323 Interval hp = head_positions (me);
324 if (dir && dir * hp[dir] >= 0)
326 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
327 SCM scm_shorten = scm_is_pair (sshorten)
328 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
329 Real shorten = 2* robust_scm2double (scm_shorten, 0);
331 /* On boundary: shorten only half */
332 if (abs (head_positions (me)[dir]) <= 1)
333 shorten *= 0.5;
335 length -= shorten;
338 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
340 /* Tremolo stuff. */
341 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
342 if (t_flag && !unsmob_grob (me->get_object ("beam")))
344 /* Crude hack: add extra space if tremolo flag is there.
346 We can't do this for the beam, since we get into a loop
347 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
349 Real minlen = 1.0
350 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
352 /* We don't want to add the whole extent of the flag because the trem
353 and the flag can overlap partly. beam_translation gives a good
354 approximation */
355 if (durlog >= 3)
357 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
358 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
359 minlen += 2 * (durlog - 1.5) * beam_trans;
361 /* up-stems need even a little more space to avoid collisions. This
362 needs to be in sync with the tremolo positioning code in
363 Stem_tremolo::print */
364 if (dir == UP)
365 minlen += beam_trans;
367 length = max (length, minlen + 1.0);
370 return scm_from_double (length);
372 /* The log of the duration (Number of hooks on the flag minus two) */
374 Stem::duration_log (Grob *me)
376 SCM s = me->get_property ("duration-log");
377 return (scm_is_number (s)) ? scm_to_int (s) : 2;
380 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
382 Stem::calc_positioning_done (SCM smob)
384 Grob *me = unsmob_grob (smob);
385 if (!head_count (me))
386 return SCM_BOOL_T;
388 me->set_property ("positioning-done", SCM_BOOL_T);
390 extract_grob_set (me, "note-heads", ro_heads);
391 vector<Grob*> heads (ro_heads);
392 vector_sort (heads, position_less);
393 Direction dir = get_grob_direction (me);
395 if (dir < 0)
396 reverse (heads);
398 Real thick = thickness (me);
400 Grob *hed = support_head (me);
401 if (!dir)
403 programming_error ("Stem dir must be up or down.");
404 dir = UP;
405 set_grob_direction (me, dir);
408 bool is_harmonic_centered = false;
409 for (vsize i = 0; i < heads.size (); i++)
410 is_harmonic_centered = is_harmonic_centered
411 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
412 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
414 Real w = hed->extent (hed, X_AXIS)[dir];
415 for (vsize i = 0; i < heads.size (); i++)
417 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
419 if (is_harmonic_centered)
420 amount =
421 hed->extent (hed, X_AXIS).linear_combination (CENTER)
422 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
424 heads[i]->translate_axis (amount, X_AXIS);
426 bool parity = true;
427 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
428 for (vsize i = 1; i < heads.size (); i++)
430 Real p = Staff_symbol_referencer::get_position (heads[i]);
431 Real dy = fabs (lastpos- p);
434 dy should always be 0.5, 0.0, 1.0, but provide safety margin
435 for rounding errors.
437 if (dy < 1.1)
439 if (parity)
441 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
443 Direction d = get_grob_direction (me);
445 Reversed head should be shifted ell-thickness, but this
446 looks too crowded, so we only shift ell-0.5*thickness.
448 This leads to assymetry: Normal heads overlap the
449 stem 100% whereas reversed heads only overlaps the
450 stem 50%
453 Real reverse_overlap = 0.5;
454 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
455 X_AXIS);
457 if (is_invisible (me))
458 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
459 X_AXIS);
461 /* TODO:
463 For some cases we should kern some more: when the
464 distance between the next or prev note is too large, we'd
465 get large white gaps, eg.
469 |X <- kern this.
475 parity = !parity;
477 else
478 parity = true;
480 lastpos = int (p);
483 return SCM_BOOL_T;
486 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
488 Stem::calc_direction (SCM smob)
490 Grob *me = unsmob_grob (smob);
491 Direction dir = CENTER;
492 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
494 SCM ignore_me = beam->get_property ("direction");
495 (void) ignore_me;
496 dir = get_grob_direction (me);
498 else
500 SCM dd = me->get_property ("default-direction");
501 dir = to_dir (dd);
502 if (!dir)
503 return me->get_property ("neutral-direction");
506 return scm_from_int (dir);
509 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
511 Stem::calc_default_direction (SCM smob)
513 Grob *me = unsmob_grob (smob);
515 Direction dir = CENTER;
516 int staff_center = 0;
517 Interval hp = head_positions (me);
518 if (!hp.is_empty ())
520 int udistance = (int) (UP * hp[UP] - staff_center);
521 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
523 dir = Direction (sign (ddistance - udistance));
526 return scm_from_int (dir);
530 MAKE_SCHEME_CALLBACK (Stem, height, 1);
532 Stem::height (SCM smob)
534 Grob *me = unsmob_grob (smob);
535 if (!is_normal_stem (me))
536 return ly_interval2scm (Interval ());
538 Direction dir = get_grob_direction (me);
540 Grob *beam = get_beam (me);
541 if (beam)
543 /* trigger set-stem-lengths. */
544 beam->get_property ("quantized-positions");
548 Can't get_stencil (), since that would cache stencils too early.
549 This causes problems with beams.
551 Stencil *stencil = unsmob_stencil (print (smob));
552 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
553 if (beam)
555 if (dir == CENTER)
557 programming_error ("no stem direction");
558 dir = UP;
560 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
563 return ly_interval2scm (iv);
566 Real
567 Stem::stem_end_position (Grob *me)
569 return robust_scm2double (me->get_property ("stem-end-position"), 0);
572 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
574 Stem::calc_flag (SCM smob)
576 Grob *me = unsmob_grob (smob);
578 int log = duration_log (me);
580 TODO: maybe property stroke-style should take different values,
581 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
582 '() or "grace"). */
583 string flag_style;
585 SCM flag_style_scm = me->get_property ("flag-style");
586 if (scm_is_symbol (flag_style_scm))
587 flag_style = ly_symbol2string (flag_style_scm);
589 if (flag_style == "no-flag")
590 return Stencil ().smobbed_copy ();
592 bool adjust = true;
594 string staffline_offs;
595 if (flag_style == "mensural")
596 /* Mensural notation: For notes on staff lines, use different
597 flags than for notes between staff lines. The idea is that
598 flags are always vertically aligned with the staff lines,
599 regardless if the note head is on a staff line or between two
600 staff lines. In other words, the inner end of a flag always
601 touches a staff line.
604 if (adjust)
606 int p = (int) (rint (stem_end_position (me)));
607 staffline_offs
608 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
610 else
611 staffline_offs = "2";
613 else
614 staffline_offs = "";
616 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
617 string font_char = flag_style
618 + to_string (dir) + staffline_offs + to_string (log);
619 Font_metric *fm = Font_interface::get_default_font (me);
620 Stencil flag = fm->find_by_name ("flags." + font_char);
621 if (flag.is_empty ())
622 me->warning (_f ("flag `%s' not found", font_char));
624 SCM stroke_style_scm = me->get_property ("stroke-style");
625 if (scm_is_string (stroke_style_scm))
627 string stroke_style = ly_scm2string (stroke_style_scm);
628 if (!stroke_style.empty ())
630 string font_char = flag_style + to_string (dir) + stroke_style;
631 Stencil stroke = fm->find_by_name ("flags." + font_char);
632 if (stroke.is_empty ())
634 font_char = to_string (dir) + stroke_style;
635 stroke = fm->find_by_name ("flags." + font_char);
637 if (stroke.is_empty ())
638 me->warning (_f ("flag stroke `%s' not found", font_char));
639 else
640 flag.add_stencil (stroke);
644 return flag.smobbed_copy ();
648 Stencil
649 Stem::flag (Grob *me)
651 int log = duration_log (me);
652 if (log < 3
653 || unsmob_grob (me->get_object ("beam")))
654 return Stencil ();
656 if (!is_normal_stem (me))
657 return Stencil ();
659 // This get_property call already evaluates the scheme function with
660 // the grob passed as argument! Thus, we only have to check if a valid
661 // stencil is returned.
662 SCM flag_style_scm = me->get_property ("flag");
663 if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
664 return *flag;
665 } else {
666 return Stencil ();
670 MAKE_SCHEME_CALLBACK (Stem, width, 1);
672 Stem::width (SCM e)
674 Grob *me = unsmob_grob (e);
676 Interval r;
678 if (is_invisible (me))
679 r.set_empty ();
680 else if (unsmob_grob (me->get_object ("beam"))
681 || abs (duration_log (me)) <= 2)
683 r = Interval (-1, 1);
684 r *= thickness (me) / 2;
686 else
688 r = Interval (-1, 1) * thickness (me) * 0.5;
689 r.unite (flag (me).extent (X_AXIS));
691 return ly_interval2scm (r);
694 Real
695 Stem::thickness (Grob *me)
697 return scm_to_double (me->get_property ("thickness"))
698 * Staff_symbol_referencer::line_thickness (me);
701 MAKE_SCHEME_CALLBACK (Stem, print, 1);
703 Stem::print (SCM smob)
705 Grob *me = unsmob_grob (smob);
706 Grob *beam = get_beam (me);
708 Stencil mol;
709 Direction d = get_grob_direction (me);
711 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
712 0.0);
713 bool stemlet = stemlet_length > 0.0;
715 /* TODO: make the stem start a direction ?
716 This is required to avoid stems passing in tablature chords. */
717 Grob *lh
718 = to_boolean (me->get_property ("avoid-note-head"))
719 ? last_head (me)
720 : first_head (me);
722 if (!lh && !stemlet)
723 return SCM_EOL;
725 if (!lh && stemlet && !beam)
726 return SCM_EOL;
728 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
729 return SCM_EOL;
731 if (is_invisible (me))
732 return SCM_EOL;
734 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
735 Real y1 = y2;
736 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
738 if (lh)
739 y2 = Staff_symbol_referencer::get_position (lh);
740 else if (stemlet)
742 Real beam_translation = Beam::get_beam_translation (beam);
743 Real beam_thickness = Beam::get_thickness (beam);
744 int beam_count = beam_multiplicity (me).length () + 1;
746 y2 -= d
747 * (0.5 * beam_thickness
748 + beam_translation * max (0, (beam_count - 1))
749 + stemlet_length) / half_space;
752 Interval stem_y (min (y1, y2), max (y2, y1));
754 if (Grob *head = support_head (me))
757 must not take ledgers into account.
759 Interval head_height = head->extent (head, Y_AXIS);
760 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
762 y_attach = head_height.linear_combination (y_attach);
763 stem_y[Direction (-d)] += d * y_attach / half_space;
766 // URG
767 Real stem_width = thickness (me);
768 Real blot
769 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
771 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
772 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
774 Stencil ss = Lookup::round_filled_box (b, blot);
775 mol.add_stencil (ss);
777 mol.add_stencil (get_translated_flag (me));
779 return mol.smobbed_copy ();
782 Stencil
783 Stem::get_translated_flag (Grob *me)
785 Stencil fl = flag (me);
786 if (!fl.is_empty ())
788 Direction d = get_grob_direction (me);
789 Real blot
790 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
791 Real stem_width = thickness (me);
792 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
793 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
794 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
795 fl.translate_axis (stem_width / 2, X_AXIS);
797 return fl;
802 move the stem to right of the notehead if it is up.
804 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
806 Stem::offset_callback (SCM smob)
808 Grob *me = unsmob_grob (smob);
810 extract_grob_set (me, "rests", rests);
811 if (rests.size ())
813 Grob *rest = rests.back ();
814 Real r = rest->extent (rest, X_AXIS).center ();
815 return scm_from_double (r);
819 if (Grob *f = first_head (me))
821 Interval head_wid = f->extent (f, X_AXIS);
822 Real attach = 0.0;
824 if (is_invisible (me))
825 attach = 0.0;
826 else
827 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
829 Direction d = get_grob_direction (me);
830 Real real_attach = head_wid.linear_combination (d * attach);
831 Real r = real_attach;
833 /* If not centered: correct for stem thickness. */
834 if (attach)
836 Real rule_thick = thickness (me);
837 r += -d * rule_thick * 0.5;
839 return scm_from_double (r);
842 programming_error ("Weird stem.");
843 return scm_from_double (0.0);
846 Spanner *
847 Stem::get_beam (Grob *me)
849 SCM b = me->get_object ("beam");
850 return dynamic_cast<Spanner *> (unsmob_grob (b));
853 Stem_info
854 Stem::get_stem_info (Grob *me)
856 Stem_info si;
857 si.dir_ = get_grob_direction (me);
859 SCM scm_info = me->get_property ("stem-info");
860 si.ideal_y_ = scm_to_double (scm_car (scm_info));
861 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
862 return si;
865 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
867 Stem::calc_stem_info (SCM smob)
869 Grob *me = unsmob_grob (smob);
870 Direction my_dir = get_grob_direction (me);
872 if (!my_dir)
874 programming_error ("no stem dir set");
875 my_dir = UP;
878 Real staff_space = Staff_symbol_referencer::staff_space (me);
879 Grob *beam = get_beam (me);
881 if (beam)
883 (void) beam->get_property ("beaming");
886 Real beam_translation = Beam::get_beam_translation (beam);
887 Real beam_thickness = Beam::get_thickness (beam);
888 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
889 Real length_fraction
890 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
892 /* Simple standard stem length */
893 SCM details = me->get_property ("details");
894 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
896 Real ideal_length
897 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
898 * staff_space
899 * length_fraction
901 /* stem only extends to center of beam
903 - 0.5 * beam_thickness;
905 /* Condition: sane minimum free stem length (chord to beams) */
906 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"), details, SCM_EOL);
908 Real ideal_minimum_free
909 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
910 * staff_space
911 * length_fraction;
913 Real height_of_my_trem = 0.0;
914 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
915 if (trem)
917 height_of_my_trem
918 = Stem_tremolo::vertical_length (trem)
919 /* hack a bit of space around the trem. */
920 + beam_translation;
924 /* UGH
925 It seems that also for ideal minimum length, we must use
926 the maximum beam count (for this direction):
928 \score{ \notes\relative c''{ [a8 a32] }}
930 must be horizontal. */
931 Real height_of_my_beams = beam_thickness
932 + (beam_count - 1) * beam_translation;
934 Real ideal_minimum_length = ideal_minimum_free
935 + height_of_my_beams
936 + height_of_my_trem
937 /* stem only extends to center of beam */
938 - 0.5 * beam_thickness;
940 ideal_length = max (ideal_length, ideal_minimum_length);
942 /* Convert to Y position, calculate for dir == UP */
943 Real note_start
944 = /* staff positions */
945 head_positions (me)[my_dir] * 0.5
946 * my_dir * staff_space;
947 Real ideal_y = note_start + ideal_length;
949 /* Conditions for Y position */
951 /* Lowest beam of (UP) beam must never be lower than second staffline
953 Reference?
955 Although this (additional) rule is probably correct,
956 I expect that highest beam (UP) should also never be lower
957 than middle staffline, just as normal stems.
959 Reference?
961 Obviously not for grace beams.
963 Also, not for knees. Seems to be a good thing. */
964 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
965 bool is_knee = to_boolean (beam->get_property ("knee"));
966 if (!no_extend && !is_knee)
968 /* Highest beam of (UP) beam must never be lower than middle
969 staffline */
970 ideal_y = max (ideal_y, 0.0);
971 /* Lowest beam of (UP) beam must never be lower than second staffline */
972 ideal_y = max (ideal_y, (-staff_space
973 - beam_thickness + height_of_my_beams));
976 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
978 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
979 details, SCM_EOL);
981 Real minimum_free
982 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
983 * staff_space
984 * length_fraction;
986 Real minimum_length = max (minimum_free, height_of_my_trem)
987 + height_of_my_beams
988 /* stem only extends to center of beam */
989 - 0.5 * beam_thickness;
991 ideal_y *= my_dir;
992 Real minimum_y = note_start + minimum_length;
993 Real shortest_y = minimum_y * my_dir;
995 return scm_list_2 (scm_from_double (ideal_y),
996 scm_from_double (shortest_y));
999 Slice
1000 Stem::beam_multiplicity (Grob *stem)
1002 SCM beaming = stem->get_property ("beaming");
1003 Slice le = int_list_to_slice (scm_car (beaming));
1004 Slice ri = int_list_to_slice (scm_cdr (beaming));
1005 le.unite (ri);
1006 return le;
1009 bool
1010 Stem::is_cross_staff (Grob *stem)
1012 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1013 return beam && Beam::is_cross_staff (beam);
1016 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1018 Stem::calc_cross_staff (SCM smob)
1020 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1023 /* FIXME: Too many properties */
1024 ADD_INTERFACE (Stem,
1025 "The stem represents the graphical stem. In addition, it"
1026 " internally connects note heads, beams, and tremolos. Rests"
1027 " and whole notes have invisible stems.\n"
1028 "\n"
1029 "The following properties may be set in the @code{details}"
1030 " list.\n"
1031 "\n"
1032 "@table @code\n"
1033 "@item beamed-lengths\n"
1034 "List of stem lengths given beam multiplicity.\n"
1035 "@item beamed-minimum-free-lengths\n"
1036 "List of normal minimum free stem lengths (chord to beams)"
1037 " given beam multiplicity.\n"
1038 "@item beamed-extreme-minimum-free-lengths\n"
1039 "List of extreme minimum free stem lengths (chord to beams)"
1040 " given beam multiplicity.\n"
1041 "@item lengths\n"
1042 "Default stem lengths. The list gives a length for each"
1043 " flag count.\n"
1044 "@item stem-shorten\n"
1045 "How much a stem in a forced direction should be shortened."
1046 " The list gives an amount depending on the number of flags"
1047 " and beams.\n"
1048 "@end table\n",
1050 /* properties */
1051 "avoid-note-head "
1052 "beam "
1053 "beaming "
1054 "beamlet-default-length "
1055 "beamlet-max-length-proportion "
1056 "default-direction "
1057 "details "
1058 "direction "
1059 "duration-log "
1060 "flag "
1061 "flag-style "
1062 "french-beaming "
1063 "length "
1064 "length-fraction "
1065 "max-beam-connect "
1066 "neutral-direction "
1067 "no-stem-extend "
1068 "note-heads "
1069 "positioning-done "
1070 "rests "
1071 "stem-end-position "
1072 "stem-info "
1073 "stemlet-length "
1074 "stroke-style "
1075 "thickness "
1076 "tremolo-flag "
1079 /****************************************************************/
1081 Stem_info::Stem_info ()
1083 ideal_y_ = shortest_y_ = 0;
1084 dir_ = CENTER;
1087 void
1088 Stem_info::scale (Real x)
1090 ideal_y_ *= x;
1091 shortest_y_ *= x;