Allow for nested properties in list form when using \override.
[lilypond.git] / lily / stem.cc
blobd50bd50cda02d8de223bbfd97e31f33f3c0f0648
1 /*
2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2007 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, SCM start, SCM end)
234 (void) start;
235 (void) end;
237 Grob *me = unsmob_grob (smob);
238 Interval iv;
240 if (!is_normal_stem (me))
241 return ly_interval2scm (iv);
243 Real ss = Staff_symbol_referencer::staff_space (me);
244 Real rad = Staff_symbol_referencer::staff_radius (me);
246 if (!to_boolean (me->get_property ("cross-staff")))
248 Real len = scm_to_double (calc_length (smob)) * ss / 2;
249 Direction dir = get_grob_direction (me);
251 Interval hp = head_positions (me);
252 if (dir == UP)
253 iv = Interval (0, len);
254 else
255 iv = Interval (-len, 0);
257 if (!hp.is_empty ())
258 iv.translate (hp[dir] * ss / 2);
260 /* extend the stem (away from the head) to cover the staff */
261 if (dir == UP)
262 iv[UP] = max (iv[UP], rad * ss);
263 else
264 iv[DOWN] = min (iv[DOWN], -rad * ss);
266 else
267 iv = Interval (-rad * ss, rad * ss);
269 return ly_interval2scm (iv);
272 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
274 Stem::calc_stem_end_position (SCM smob)
276 Grob *me = unsmob_grob (smob);
278 if (!head_count (me))
279 return scm_from_double (0.0);
281 if (Grob *beam = get_beam (me))
283 (void) beam->get_property ("quantized-positions");
284 return me->get_property ("stem-end-position");
287 vector<Real> a;
289 /* WARNING: IN HALF SPACES */
290 Real length = robust_scm2double (me->get_property ("length"), 7);
292 Direction dir = get_grob_direction (me);
293 Interval hp = head_positions (me);
294 Real stem_end = dir ? hp[dir] + dir * length : 0;
296 /* TODO: change name to extend-stems to staff/center/'() */
297 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
298 if (!no_extend && dir * stem_end < 0)
299 stem_end = 0.0;
301 return scm_from_double (stem_end);
304 /* Length is in half-spaces (or: positions) here. */
305 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
307 Stem::calc_length (SCM smob)
309 Grob *me = unsmob_grob (smob);
311 SCM details = me->get_property ("details");
312 int durlog = duration_log (me);
314 Real ss = Staff_symbol_referencer::staff_space (me);
315 Real length = 7;
316 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
317 if (scm_is_pair (s))
318 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
320 Direction dir = get_grob_direction (me);
322 /* Stems in unnatural (forced) direction should be shortened,
323 according to [Roush & Gourlay] */
324 Interval hp = head_positions (me);
325 if (dir && dir * hp[dir] >= 0)
327 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
328 SCM scm_shorten = scm_is_pair (sshorten)
329 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
330 Real shorten = 2* robust_scm2double (scm_shorten, 0);
332 /* On boundary: shorten only half */
333 if (abs (head_positions (me)[dir]) <= 1)
334 shorten *= 0.5;
336 length -= shorten;
339 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
341 /* Tremolo stuff. */
342 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
343 if (t_flag && !unsmob_grob (me->get_object ("beam")))
345 /* Crude hack: add extra space if tremolo flag is there.
347 We can't do this for the beam, since we get into a loop
348 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
350 Real minlen = 1.0
351 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
353 /* We don't want to add the whole extent of the flag because the trem
354 and the flag can overlap partly. beam_translation gives a good
355 approximation */
356 if (durlog >= 3)
358 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
359 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
360 minlen += 2 * (durlog - 1.5) * beam_trans;
362 /* up-stems need even a little more space to avoid collisions. This
363 needs to be in sync with the tremolo positioning code in
364 Stem_tremolo::print */
365 if (dir == UP)
366 minlen += beam_trans;
368 length = max (length, minlen + 1.0);
371 return scm_from_double (length);
373 /* The log of the duration (Number of hooks on the flag minus two) */
375 Stem::duration_log (Grob *me)
377 SCM s = me->get_property ("duration-log");
378 return (scm_is_number (s)) ? scm_to_int (s) : 2;
381 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
383 Stem::calc_positioning_done (SCM smob)
385 Grob *me = unsmob_grob (smob);
386 if (!head_count (me))
387 return SCM_BOOL_T;
389 me->set_property ("positioning-done", SCM_BOOL_T);
391 extract_grob_set (me, "note-heads", ro_heads);
392 vector<Grob*> heads (ro_heads);
393 vector_sort (heads, position_less);
394 Direction dir = get_grob_direction (me);
396 if (dir < 0)
397 reverse (heads);
399 Real thick = thickness (me);
401 Grob *hed = support_head (me);
402 if (!dir)
404 programming_error ("Stem dir must be up or down.");
405 dir = UP;
406 set_grob_direction (me, dir);
409 bool is_harmonic_centered = false;
410 for (vsize i = 0; i < heads.size (); i++)
411 is_harmonic_centered = is_harmonic_centered
412 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
413 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
415 Real w = hed->extent (hed, X_AXIS)[dir];
416 for (vsize i = 0; i < heads.size (); i++)
418 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
420 if (is_harmonic_centered)
421 amount =
422 hed->extent (hed, X_AXIS).linear_combination (CENTER)
423 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
425 heads[i]->translate_axis (amount, X_AXIS);
427 bool parity = true;
428 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
429 for (vsize i = 1; i < heads.size (); i++)
431 Real p = Staff_symbol_referencer::get_position (heads[i]);
432 Real dy = fabs (lastpos- p);
435 dy should always be 0.5, 0.0, 1.0, but provide safety margin
436 for rounding errors.
438 if (dy < 1.1)
440 if (parity)
442 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
444 Direction d = get_grob_direction (me);
446 Reversed head should be shifted ell-thickness, but this
447 looks too crowded, so we only shift ell-0.5*thickness.
449 This leads to assymetry: Normal heads overlap the
450 stem 100% whereas reversed heads only overlaps the
451 stem 50%
454 Real reverse_overlap = 0.5;
455 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
456 X_AXIS);
458 if (is_invisible (me))
459 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
460 X_AXIS);
462 /* TODO:
464 For some cases we should kern some more: when the
465 distance between the next or prev note is too large, we'd
466 get large white gaps, eg.
470 |X <- kern this.
476 parity = !parity;
478 else
479 parity = true;
481 lastpos = int (p);
484 return SCM_BOOL_T;
487 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
489 Stem::calc_direction (SCM smob)
491 Grob *me = unsmob_grob (smob);
492 Direction dir = CENTER;
493 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
495 SCM ignore_me = beam->get_property ("direction");
496 (void) ignore_me;
497 dir = get_grob_direction (me);
499 else
501 SCM dd = me->get_property ("default-direction");
502 dir = to_dir (dd);
503 if (!dir)
504 return me->get_property ("neutral-direction");
507 return scm_from_int (dir);
510 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
512 Stem::calc_default_direction (SCM smob)
514 Grob *me = unsmob_grob (smob);
516 Direction dir = CENTER;
517 int staff_center = 0;
518 Interval hp = head_positions (me);
519 if (!hp.is_empty ())
521 int udistance = (int) (UP * hp[UP] - staff_center);
522 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
524 dir = Direction (sign (ddistance - udistance));
527 return scm_from_int (dir);
531 MAKE_SCHEME_CALLBACK (Stem, height, 1);
533 Stem::height (SCM smob)
535 Grob *me = unsmob_grob (smob);
536 if (!is_normal_stem (me))
537 return ly_interval2scm (Interval ());
539 Direction dir = get_grob_direction (me);
541 Grob *beam = get_beam (me);
542 if (beam)
544 /* trigger set-stem-lengths. */
545 beam->get_property ("quantized-positions");
549 Can't get_stencil (), since that would cache stencils too early.
550 This causes problems with beams.
552 Stencil *stencil = unsmob_stencil (print (smob));
553 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
554 if (beam)
556 if (dir == CENTER)
558 programming_error ("no stem direction");
559 dir = UP;
561 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
564 return ly_interval2scm (iv);
567 Real
568 Stem::stem_end_position (Grob *me)
570 return robust_scm2double (me->get_property ("stem-end-position"), 0);
573 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
575 Stem::calc_flag (SCM smob)
577 Grob *me = unsmob_grob (smob);
579 int log = duration_log (me);
581 TODO: maybe property stroke-style should take different values,
582 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
583 '() or "grace"). */
584 string flag_style;
586 SCM flag_style_scm = me->get_property ("flag-style");
587 if (scm_is_symbol (flag_style_scm))
588 flag_style = ly_symbol2string (flag_style_scm);
590 if (flag_style == "no-flag")
591 return Stencil ().smobbed_copy ();
593 bool adjust = true;
595 string staffline_offs;
596 if (flag_style == "mensural")
597 /* Mensural notation: For notes on staff lines, use different
598 flags than for notes between staff lines. The idea is that
599 flags are always vertically aligned with the staff lines,
600 regardless if the note head is on a staff line or between two
601 staff lines. In other words, the inner end of a flag always
602 touches a staff line.
605 if (adjust)
607 int p = (int) (rint (stem_end_position (me)));
608 staffline_offs
609 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
611 else
612 staffline_offs = "2";
614 else
615 staffline_offs = "";
617 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
618 string font_char = flag_style
619 + to_string (dir) + staffline_offs + to_string (log);
620 Font_metric *fm = Font_interface::get_default_font (me);
621 Stencil flag = fm->find_by_name ("flags." + font_char);
622 if (flag.is_empty ())
623 me->warning (_f ("flag `%s' not found", font_char));
625 SCM stroke_style_scm = me->get_property ("stroke-style");
626 if (scm_is_string (stroke_style_scm))
628 string stroke_style = ly_scm2string (stroke_style_scm);
629 if (!stroke_style.empty ())
631 string font_char = flag_style + to_string (dir) + stroke_style;
632 Stencil stroke = fm->find_by_name ("flags." + font_char);
633 if (stroke.is_empty ())
635 font_char = to_string (dir) + stroke_style;
636 stroke = fm->find_by_name ("flags." + font_char);
638 if (stroke.is_empty ())
639 me->warning (_f ("flag stroke `%s' not found", font_char));
640 else
641 flag.add_stencil (stroke);
645 return flag.smobbed_copy ();
649 Stencil
650 Stem::flag (Grob *me)
652 int log = duration_log (me);
653 if (log < 3
654 || unsmob_grob (me->get_object ("beam")))
655 return Stencil ();
657 if (!is_normal_stem (me))
658 return Stencil ();
660 // This get_property call already evaluates the scheme function with
661 // the grob passed as argument! Thus, we only have to check if a valid
662 // stencil is returned.
663 SCM flag_style_scm = me->get_property ("flag");
664 if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
665 return *flag;
666 } else {
667 return Stencil ();
671 MAKE_SCHEME_CALLBACK (Stem, width, 1);
673 Stem::width (SCM e)
675 Grob *me = unsmob_grob (e);
677 Interval r;
679 if (is_invisible (me))
680 r.set_empty ();
681 else if (unsmob_grob (me->get_object ("beam"))
682 || abs (duration_log (me)) <= 2)
684 r = Interval (-1, 1);
685 r *= thickness (me) / 2;
687 else
689 r = Interval (-1, 1) * thickness (me) * 0.5;
690 r.unite (flag (me).extent (X_AXIS));
692 return ly_interval2scm (r);
695 Real
696 Stem::thickness (Grob *me)
698 return scm_to_double (me->get_property ("thickness"))
699 * Staff_symbol_referencer::line_thickness (me);
702 MAKE_SCHEME_CALLBACK (Stem, print, 1);
704 Stem::print (SCM smob)
706 Grob *me = unsmob_grob (smob);
707 Grob *beam = get_beam (me);
709 Stencil mol;
710 Direction d = get_grob_direction (me);
712 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
713 0.0);
714 bool stemlet = stemlet_length > 0.0;
716 /* TODO: make the stem start a direction ?
717 This is required to avoid stems passing in tablature chords. */
718 Grob *lh
719 = to_boolean (me->get_property ("avoid-note-head"))
720 ? last_head (me)
721 : first_head (me);
723 if (!lh && !stemlet)
724 return SCM_EOL;
726 if (!lh && stemlet && !beam)
727 return SCM_EOL;
729 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
730 return SCM_EOL;
732 if (is_invisible (me))
733 return SCM_EOL;
735 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
736 Real y1 = y2;
737 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
739 if (lh)
740 y2 = Staff_symbol_referencer::get_position (lh);
741 else if (stemlet)
743 Real beam_translation = Beam::get_beam_translation (beam);
744 Real beam_thickness = Beam::get_thickness (beam);
745 int beam_count = beam_multiplicity (me).length () + 1;
747 y2 -= d
748 * (0.5 * beam_thickness
749 + beam_translation * max (0, (beam_count - 1))
750 + stemlet_length) / half_space;
753 Interval stem_y (min (y1, y2), max (y2, y1));
755 if (Grob *head = support_head (me))
758 must not take ledgers into account.
760 Interval head_height = head->extent (head, Y_AXIS);
761 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
763 y_attach = head_height.linear_combination (y_attach);
764 stem_y[Direction (-d)] += d * y_attach / half_space;
767 // URG
768 Real stem_width = thickness (me);
769 Real blot
770 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
772 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
773 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
775 Stencil ss = Lookup::round_filled_box (b, blot);
776 mol.add_stencil (ss);
778 mol.add_stencil (get_translated_flag (me));
780 return mol.smobbed_copy ();
783 Stencil
784 Stem::get_translated_flag (Grob *me)
786 Stencil fl = flag (me);
787 if (!fl.is_empty ())
789 Direction d = get_grob_direction (me);
790 Real blot
791 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
792 Real stem_width = thickness (me);
793 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
794 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
795 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
796 fl.translate_axis (stem_width / 2, X_AXIS);
798 return fl;
803 move the stem to right of the notehead if it is up.
805 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
807 Stem::offset_callback (SCM smob)
809 Grob *me = unsmob_grob (smob);
811 extract_grob_set (me, "rests", rests);
812 if (rests.size ())
814 Grob *rest = rests.back ();
815 Real r = rest->extent (rest, X_AXIS).center ();
816 return scm_from_double (r);
820 if (Grob *f = first_head (me))
822 Interval head_wid = f->extent (f, X_AXIS);
823 Real attach = 0.0;
825 if (is_invisible (me))
826 attach = 0.0;
827 else
828 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
830 Direction d = get_grob_direction (me);
831 Real real_attach = head_wid.linear_combination (d * attach);
832 Real r = real_attach;
834 /* If not centered: correct for stem thickness. */
835 if (attach)
837 Real rule_thick = thickness (me);
838 r += -d * rule_thick * 0.5;
840 return scm_from_double (r);
843 programming_error ("Weird stem.");
844 return scm_from_double (0.0);
847 Spanner *
848 Stem::get_beam (Grob *me)
850 SCM b = me->get_object ("beam");
851 return dynamic_cast<Spanner *> (unsmob_grob (b));
854 Stem_info
855 Stem::get_stem_info (Grob *me)
857 Stem_info si;
858 si.dir_ = get_grob_direction (me);
860 SCM scm_info = me->get_property ("stem-info");
861 si.ideal_y_ = scm_to_double (scm_car (scm_info));
862 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
863 return si;
866 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
868 Stem::calc_stem_info (SCM smob)
870 Grob *me = unsmob_grob (smob);
871 Direction my_dir = get_grob_direction (me);
873 if (!my_dir)
875 programming_error ("no stem dir set");
876 my_dir = UP;
879 Real staff_space = Staff_symbol_referencer::staff_space (me);
880 Grob *beam = get_beam (me);
882 if (beam)
884 (void) beam->get_property ("beaming");
887 Real beam_translation = Beam::get_beam_translation (beam);
888 Real beam_thickness = Beam::get_thickness (beam);
889 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
890 Real length_fraction
891 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
893 /* Simple standard stem length */
894 SCM details = me->get_property ("details");
895 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
897 Real ideal_length
898 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
899 * staff_space
900 * length_fraction
902 /* stem only extends to center of beam
904 - 0.5 * beam_thickness;
906 /* Condition: sane minimum free stem length (chord to beams) */
907 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"), details, SCM_EOL);
909 Real ideal_minimum_free
910 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
911 * staff_space
912 * length_fraction;
914 Real height_of_my_trem = 0.0;
915 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
916 if (trem)
918 height_of_my_trem
919 = Stem_tremolo::vertical_length (trem)
920 /* hack a bit of space around the trem. */
921 + beam_translation;
925 /* UGH
926 It seems that also for ideal minimum length, we must use
927 the maximum beam count (for this direction):
929 \score{ \notes\relative c''{ [a8 a32] }}
931 must be horizontal. */
932 Real height_of_my_beams = beam_thickness
933 + (beam_count - 1) * beam_translation;
935 Real ideal_minimum_length = ideal_minimum_free
936 + height_of_my_beams
937 + height_of_my_trem
938 /* stem only extends to center of beam */
939 - 0.5 * beam_thickness;
941 ideal_length = max (ideal_length, ideal_minimum_length);
943 /* Convert to Y position, calculate for dir == UP */
944 Real note_start
945 = /* staff positions */
946 head_positions (me)[my_dir] * 0.5
947 * my_dir * staff_space;
948 Real ideal_y = note_start + ideal_length;
950 /* Conditions for Y position */
952 /* Lowest beam of (UP) beam must never be lower than second staffline
954 Reference?
956 Although this (additional) rule is probably correct,
957 I expect that highest beam (UP) should also never be lower
958 than middle staffline, just as normal stems.
960 Reference?
962 Obviously not for grace beams.
964 Also, not for knees. Seems to be a good thing. */
965 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
966 bool is_knee = to_boolean (beam->get_property ("knee"));
967 if (!no_extend && !is_knee)
969 /* Highest beam of (UP) beam must never be lower than middle
970 staffline */
971 ideal_y = max (ideal_y, 0.0);
972 /* Lowest beam of (UP) beam must never be lower than second staffline */
973 ideal_y = max (ideal_y, (-staff_space
974 - beam_thickness + height_of_my_beams));
977 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
979 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
980 details, SCM_EOL);
982 Real minimum_free
983 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
984 * staff_space
985 * length_fraction;
987 Real minimum_length = max (minimum_free, height_of_my_trem)
988 + height_of_my_beams
989 /* stem only extends to center of beam */
990 - 0.5 * beam_thickness;
992 ideal_y *= my_dir;
993 Real minimum_y = note_start + minimum_length;
994 Real shortest_y = minimum_y * my_dir;
996 return scm_list_2 (scm_from_double (ideal_y),
997 scm_from_double (shortest_y));
1000 Slice
1001 Stem::beam_multiplicity (Grob *stem)
1003 SCM beaming = stem->get_property ("beaming");
1004 Slice le = int_list_to_slice (scm_car (beaming));
1005 Slice ri = int_list_to_slice (scm_cdr (beaming));
1006 le.unite (ri);
1007 return le;
1010 bool
1011 Stem::is_cross_staff (Grob *stem)
1013 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1014 return beam && Beam::is_cross_staff (beam);
1017 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1019 Stem::calc_cross_staff (SCM smob)
1021 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1024 /* FIXME: Too many properties */
1025 ADD_INTERFACE (Stem,
1026 "The stem represents the graphical stem. In addition, it"
1027 " internally connects note heads, beams, and tremolos. Rests"
1028 " and whole notes have invisible stems.\n"
1029 "\n"
1030 "The following properties may be set in the @code{details}"
1031 " list.\n"
1032 "\n"
1033 "@table @code\n"
1034 "@item beamed-lengths\n"
1035 "List of stem lengths given beam multiplicity.\n"
1036 "@item beamed-minimum-free-lengths\n"
1037 "List of normal minimum free stem lengths (chord to beams)"
1038 " given beam multiplicity.\n"
1039 "@item beamed-extreme-minimum-free-lengths\n"
1040 "List of extreme minimum free stem lengths (chord to beams)"
1041 " given beam multiplicity.\n"
1042 "@item lengths\n"
1043 "Default stem lengths. The list gives a length for each"
1044 " flag count.\n"
1045 "@item stem-shorten\n"
1046 "How much a stem in a forced direction should be shortened."
1047 " The list gives an amount depending on the number of flags"
1048 " and beams.\n"
1049 "@end table\n",
1051 /* properties */
1052 "avoid-note-head "
1053 "beam "
1054 "beaming "
1055 "default-direction "
1056 "details "
1057 "direction "
1058 "duration-log "
1059 "flag "
1060 "flag-style "
1061 "french-beaming "
1062 "length "
1063 "length-fraction "
1064 "max-beam-connect "
1065 "neutral-direction "
1066 "no-stem-extend "
1067 "note-heads "
1068 "positioning-done "
1069 "rests "
1070 "stem-end-position "
1071 "stem-info "
1072 "stemlet-length "
1073 "stroke-style "
1074 "thickness "
1075 "tremolo-flag "
1078 /****************************************************************/
1080 Stem_info::Stem_info ()
1082 ideal_y_ = shortest_y_ = 0;
1083 dir_ = CENTER;
1086 void
1087 Stem_info::scale (Real x)
1089 ideal_y_ *= x;
1090 shortest_y_ *= x;