Fix segfault triggered by invalid Stem 'details settings.
[lilypond/mpolesky.git] / lily / stem.cc
blob314f74e2b42108d7062c6d85238320bb283327df
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
7 TODO: This is way too hairy
9 TODO: fix naming.
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/>.
27 #include "stem.hh"
28 #include "spanner.hh"
30 #include <cmath> // rint
31 using namespace std;
33 #include "beam.hh"
34 #include "directional-element-interface.hh"
35 #include "dot-column.hh"
36 #include "font-interface.hh"
37 #include "international.hh"
38 #include "lookup.hh"
39 #include "misc.hh"
40 #include "note-head.hh"
41 #include "output-def.hh"
42 #include "paper-column.hh"
43 #include "pointer-group-interface.hh"
44 #include "rest.hh"
45 #include "rhythmic-head.hh"
46 #include "side-position-interface.hh"
47 #include "staff-symbol-referencer.hh"
48 #include "stem-tremolo.hh"
49 #include "warn.hh"
51 void
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);
63 if (beam_count)
64 for (int i = 0; i < beam_count; i++)
65 lst = scm_cons (scm_from_int (i), lst);
66 else
67 lst = SCM_BOOL_F;
69 index_set_cell (pair, d, lst);
72 int
73 Stem::get_beaming (Grob *me, Direction d)
75 SCM pair = me->get_property ("beaming");
76 if (!scm_is_pair (pair))
77 return 0;
79 SCM lst = index_get_cell (pair, d);
81 int len = scm_ilength (lst);
82 return max (len, 0);
85 Interval
86 Stem::head_positions (Grob *me)
88 if (head_count (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]));
94 return Interval ();
97 Real
98 Stem::chord_start_y (Grob *me)
100 Interval hp = head_positions (me);
101 if (!hp.is_empty ())
102 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
103 * 0.5;
104 return 0;
109 void
110 Stem::set_stemend (Grob *me, Real se)
112 // todo: margins
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 */
123 Grob *
124 Stem::support_head (Grob *me)
126 extract_grob_set (me, "note-heads", heads);
127 if (heads.size () == 1)
128 return heads[0];
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 */
141 Grob *
142 Stem::first_head (Grob *me)
144 Direction d = get_grob_direction (me);
145 if (d)
146 return extremal_heads (me)[-d];
147 return 0;
150 /* The note head opposite to the first head. */
151 Grob *
152 Stem::last_head (Grob *me)
154 Direction d = get_grob_direction (me);
155 if (d)
156 return extremal_heads (me)[d];
157 return 0;
161 START is part where stem reaches `last' head.
163 This function returns a drul with (bottom-head, top-head).
165 Drul_array<Grob *>
166 Stem::extremal_heads (Grob *me)
168 const int inf = INT_MAX;
169 Drul_array<int> extpos;
170 extpos[DOWN] = inf;
171 extpos[UP] = -inf;
173 Drul_array<Grob *> exthead (0, 0);
174 extract_grob_set (me, "note-heads", heads);
176 for (vsize i = heads.size (); i--;)
178 Grob *n = heads[i];
179 int p = Staff_symbol_referencer::get_rounded_position (n);
181 Direction d = LEFT;
184 if (d * p > d * extpos[d])
186 exthead[d] = n;
187 extpos[d] = p;
190 while (flip (&d) != DOWN);
192 return exthead;
195 /* The positions, in ascending order. */
196 vector<int>
197 Stem::note_head_positions (Grob *me)
199 vector<int> ps;
200 extract_grob_set (me, "note-heads", heads);
202 for (vsize i = heads.size (); i--;)
204 Grob *n = heads[i];
205 int p = Staff_symbol_referencer::get_rounded_position (n);
207 ps.push_back (p);
210 vector_sort (ps, less<int> ());
211 return ps;
214 void
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);
225 bool
226 Stem::is_invisible (Grob *me)
228 return !is_normal_stem (me)
229 && (robust_scm2double (me->get_property ("stemlet-length"),
230 0.0) == 0.0);
234 bool
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,
244 SCM /* start */,
245 SCM /* end */)
247 Grob *me = unsmob_grob (smob);
248 Interval iv;
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 = scm_to_double (calc_length (smob)) * ss / 2;
259 Direction dir = get_grob_direction (me);
261 Interval hp = head_positions (me);
262 if (dir == UP)
263 iv = Interval (0, len);
264 else
265 iv = Interval (-len, 0);
267 if (!hp.is_empty ())
269 iv.translate (hp[dir] * ss / 2);
270 iv.add_point (hp[-dir] * ss / 2);
273 /* extend the stem (away from the head) to cover the staff */
274 if (dir == UP)
275 iv[UP] = max (iv[UP], rad * ss);
276 else
277 iv[DOWN] = min (iv[DOWN], -rad * ss);
279 else
280 iv = Interval (-rad * ss, rad * ss);
282 return ly_interval2scm (iv);
285 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
287 Stem::calc_stem_end_position (SCM smob)
289 Grob *me = unsmob_grob (smob);
291 if (!head_count (me))
292 return scm_from_double (0.0);
294 if (Grob *beam = get_beam (me))
296 (void) beam->get_property ("quantized-positions");
297 return me->get_property ("stem-end-position");
300 vector<Real> a;
302 /* WARNING: IN HALF SPACES */
303 Real length = robust_scm2double (me->get_property ("length"), 7);
305 Direction dir = get_grob_direction (me);
306 Interval hp = head_positions (me);
307 Real stem_end = dir ? hp[dir] + dir * length : 0;
309 /* TODO: change name to extend-stems to staff/center/'() */
310 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
311 if (!no_extend && dir * stem_end < 0)
312 stem_end = 0.0;
314 return scm_from_double (stem_end);
317 /* Length is in half-spaces (or: positions) here. */
318 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
320 Stem::calc_length (SCM smob)
322 Grob *me = unsmob_grob (smob);
324 SCM details = me->get_property ("details");
325 int durlog = duration_log (me);
327 Real ss = Staff_symbol_referencer::staff_space (me);
328 Real length = 7;
329 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
330 if (scm_is_pair (s))
331 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
333 Direction dir = get_grob_direction (me);
335 /* Stems in unnatural (forced) direction should be shortened,
336 according to [Roush & Gourlay] */
337 Interval hp = head_positions (me);
338 if (dir && dir * hp[dir] >= 0)
340 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
341 SCM scm_shorten = scm_is_pair (sshorten)
342 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
343 Real shorten = 2* robust_scm2double (scm_shorten, 0);
345 /* On boundary: shorten only half */
346 if (abs (head_positions (me)[dir]) <= 1)
347 shorten *= 0.5;
349 length -= shorten;
352 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
354 /* Tremolo stuff. */
355 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
356 if (t_flag && !unsmob_grob (me->get_object ("beam")))
358 /* Crude hack: add extra space if tremolo flag is there.
360 We can't do this for the beam, since we get into a loop
361 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
363 Real minlen = 1.0
364 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
366 /* We don't want to add the whole extent of the flag because the trem
367 and the flag can overlap partly. beam_translation gives a good
368 approximation */
369 if (durlog >= 3)
371 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
372 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
373 minlen += 2 * (durlog - 1.5) * beam_trans;
375 /* up-stems need even a little more space to avoid collisions. This
376 needs to be in sync with the tremolo positioning code in
377 Stem_tremolo::print */
378 if (dir == UP)
379 minlen += beam_trans;
381 length = max (length, minlen + 1.0);
384 return scm_from_double (length);
386 /* The log of the duration (Number of hooks on the flag minus two) */
388 Stem::duration_log (Grob *me)
390 SCM s = me->get_property ("duration-log");
391 return (scm_is_number (s)) ? scm_to_int (s) : 2;
394 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
396 Stem::calc_positioning_done (SCM smob)
398 Grob *me = unsmob_grob (smob);
399 if (!head_count (me))
400 return SCM_BOOL_T;
402 me->set_property ("positioning-done", SCM_BOOL_T);
404 extract_grob_set (me, "note-heads", ro_heads);
405 vector<Grob*> heads (ro_heads);
406 vector_sort (heads, position_less);
407 Direction dir = get_grob_direction (me);
409 if (dir < 0)
410 reverse (heads);
412 Real thick = thickness (me);
414 Grob *hed = support_head (me);
415 if (!dir)
417 programming_error ("Stem dir must be up or down.");
418 dir = UP;
419 set_grob_direction (me, dir);
422 bool is_harmonic_centered = false;
423 for (vsize i = 0; i < heads.size (); i++)
424 is_harmonic_centered = is_harmonic_centered
425 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
426 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
428 Real w = hed->extent (hed, X_AXIS)[dir];
429 for (vsize i = 0; i < heads.size (); i++)
431 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
433 if (is_harmonic_centered)
434 amount =
435 hed->extent (hed, X_AXIS).linear_combination (CENTER)
436 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
438 heads[i]->translate_axis (amount, X_AXIS);
440 bool parity = true;
441 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
442 for (vsize i = 1; i < heads.size (); i++)
444 Real p = Staff_symbol_referencer::get_position (heads[i]);
445 Real dy = fabs (lastpos- p);
448 dy should always be 0.5, 0.0, 1.0, but provide safety margin
449 for rounding errors.
451 if (dy < 1.1)
453 if (parity)
455 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
457 Direction d = get_grob_direction (me);
459 Reversed head should be shifted ell-thickness, but this
460 looks too crowded, so we only shift ell-0.5*thickness.
462 This leads to assymetry: Normal heads overlap the
463 stem 100% whereas reversed heads only overlaps the
464 stem 50%
467 Real reverse_overlap = 0.5;
468 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
469 X_AXIS);
471 if (is_invisible (me))
472 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
473 X_AXIS);
475 /* TODO:
477 For some cases we should kern some more: when the
478 distance between the next or prev note is too large, we'd
479 get large white gaps, eg.
483 |X <- kern this.
489 parity = !parity;
491 else
492 parity = true;
494 lastpos = int (p);
497 return SCM_BOOL_T;
500 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
502 Stem::calc_direction (SCM smob)
504 Grob *me = unsmob_grob (smob);
505 Direction dir = CENTER;
506 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
508 SCM ignore_me = beam->get_property ("direction");
509 (void) ignore_me;
510 dir = get_grob_direction (me);
512 else
514 SCM dd = me->get_property ("default-direction");
515 dir = to_dir (dd);
516 if (!dir)
517 return me->get_property ("neutral-direction");
520 return scm_from_int (dir);
523 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
525 Stem::calc_default_direction (SCM smob)
527 Grob *me = unsmob_grob (smob);
529 Direction dir = CENTER;
530 int staff_center = 0;
531 Interval hp = head_positions (me);
532 if (!hp.is_empty ())
534 int udistance = (int) (UP * hp[UP] - staff_center);
535 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
537 dir = Direction (sign (ddistance - udistance));
540 return scm_from_int (dir);
544 MAKE_SCHEME_CALLBACK (Stem, height, 1);
546 Stem::height (SCM smob)
548 Grob *me = unsmob_grob (smob);
549 if (!is_normal_stem (me))
550 return ly_interval2scm (Interval ());
552 Direction dir = get_grob_direction (me);
554 Grob *beam = get_beam (me);
555 if (beam)
557 /* trigger set-stem-lengths. */
558 beam->get_property ("quantized-positions");
562 Can't get_stencil (), since that would cache stencils too early.
563 This causes problems with beams.
565 Stencil *stencil = unsmob_stencil (print (smob));
566 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
567 if (beam)
569 if (dir == CENTER)
571 programming_error ("no stem direction");
572 dir = UP;
574 iv[dir] += dir * Beam::get_beam_thickness (beam) * 0.5;
577 return ly_interval2scm (iv);
580 Real
581 Stem::stem_end_position (Grob *me)
583 return robust_scm2double (me->get_property ("stem-end-position"), 0);
586 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
588 Stem::calc_flag (SCM smob)
590 Grob *me = unsmob_grob (smob);
592 int log = duration_log (me);
594 TODO: maybe property stroke-style should take different values,
595 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
596 '() or "grace"). */
597 string flag_style;
599 SCM flag_style_scm = me->get_property ("flag-style");
600 if (scm_is_symbol (flag_style_scm))
601 flag_style = ly_symbol2string (flag_style_scm);
603 if (flag_style == "no-flag")
604 return Stencil ().smobbed_copy ();
606 bool adjust = true;
608 string staffline_offs;
609 if (flag_style == "mensural")
610 /* Mensural notation: For notes on staff lines, use different
611 flags than for notes between staff lines. The idea is that
612 flags are always vertically aligned with the staff lines,
613 regardless if the note head is on a staff line or between two
614 staff lines. In other words, the inner end of a flag always
615 touches a staff line.
618 if (adjust)
620 int p = (int) (rint (stem_end_position (me)));
621 staffline_offs
622 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
624 else
625 staffline_offs = "2";
627 else
628 staffline_offs = "";
630 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
631 string font_char = flag_style
632 + to_string (dir) + staffline_offs + to_string (log);
633 Font_metric *fm = Font_interface::get_default_font (me);
634 Stencil flag = fm->find_by_name ("flags." + font_char);
635 if (flag.is_empty ())
636 me->warning (_f ("flag `%s' not found", font_char));
638 SCM stroke_style_scm = me->get_property ("stroke-style");
639 if (scm_is_string (stroke_style_scm))
641 string stroke_style = ly_scm2string (stroke_style_scm);
642 if (!stroke_style.empty ())
644 string font_char = flag_style + to_string (dir) + stroke_style;
645 Stencil stroke = fm->find_by_name ("flags." + font_char);
646 if (stroke.is_empty ())
648 font_char = to_string (dir) + stroke_style;
649 stroke = fm->find_by_name ("flags." + font_char);
651 if (stroke.is_empty ())
652 me->warning (_f ("flag stroke `%s' not found", font_char));
653 else
654 flag.add_stencil (stroke);
658 return flag.smobbed_copy ();
662 Stencil
663 Stem::flag (Grob *me)
665 int log = duration_log (me);
666 if (log < 3
667 || unsmob_grob (me->get_object ("beam")))
668 return Stencil ();
670 if (!is_normal_stem (me))
671 return Stencil ();
673 // This get_property call already evaluates the scheme function with
674 // the grob passed as argument! Thus, we only have to check if a valid
675 // stencil is returned.
676 SCM flag_style_scm = me->get_property ("flag");
677 if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
678 return *flag;
679 } else {
680 return Stencil ();
684 MAKE_SCHEME_CALLBACK (Stem, width, 1);
686 Stem::width (SCM e)
688 Grob *me = unsmob_grob (e);
690 Interval r;
692 if (is_invisible (me))
693 r.set_empty ();
694 else if (unsmob_grob (me->get_object ("beam"))
695 || abs (duration_log (me)) <= 2)
697 r = Interval (-1, 1);
698 r *= thickness (me) / 2;
700 else
702 r = Interval (-1, 1) * thickness (me) * 0.5;
703 r.unite (flag (me).extent (X_AXIS));
705 return ly_interval2scm (r);
708 Real
709 Stem::thickness (Grob *me)
711 return scm_to_double (me->get_property ("thickness"))
712 * Staff_symbol_referencer::line_thickness (me);
715 MAKE_SCHEME_CALLBACK (Stem, print, 1);
717 Stem::print (SCM smob)
719 Grob *me = unsmob_grob (smob);
720 Grob *beam = get_beam (me);
722 Stencil mol;
723 Direction d = get_grob_direction (me);
725 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
726 0.0);
727 bool stemlet = stemlet_length > 0.0;
729 /* TODO: make the stem start a direction ?
730 This is required to avoid stems passing in tablature chords. */
731 Grob *lh
732 = to_boolean (me->get_property ("avoid-note-head"))
733 ? last_head (me)
734 : first_head (me);
736 if (!lh && !stemlet)
737 return SCM_EOL;
739 if (!lh && stemlet && !beam)
740 return SCM_EOL;
742 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
743 return SCM_EOL;
745 if (is_invisible (me))
746 return SCM_EOL;
748 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
749 Real y1 = y2;
750 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
752 if (lh)
753 y2 = Staff_symbol_referencer::get_position (lh);
754 else if (stemlet)
756 Real beam_translation = Beam::get_beam_translation (beam);
757 Real beam_thickness = Beam::get_beam_thickness (beam);
758 int beam_count = beam_multiplicity (me).length () + 1;
760 y2 -= d
761 * (0.5 * beam_thickness
762 + beam_translation * max (0, (beam_count - 1))
763 + stemlet_length) / half_space;
766 Interval stem_y (min (y1, y2), max (y2, y1));
768 if (Grob *head = support_head (me))
771 must not take ledgers into account.
773 Interval head_height = head->extent (head, Y_AXIS);
774 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
776 y_attach = head_height.linear_combination (y_attach);
777 stem_y[Direction (-d)] += d * y_attach / half_space;
780 // URG
781 Real stem_width = thickness (me);
782 Real blot
783 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
785 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
786 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
788 Stencil ss = Lookup::round_filled_box (b, blot);
789 mol.add_stencil (ss);
791 mol.add_stencil (get_translated_flag (me));
793 return mol.smobbed_copy ();
796 Stencil
797 Stem::get_translated_flag (Grob *me)
799 Stencil fl = flag (me);
800 if (!fl.is_empty ())
802 Direction d = get_grob_direction (me);
803 Real blot
804 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
805 Real stem_width = thickness (me);
806 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
807 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
808 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
809 fl.translate_axis (stem_width / 2, X_AXIS);
811 return fl;
816 move the stem to right of the notehead if it is up.
818 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
820 Stem::offset_callback (SCM smob)
822 Grob *me = unsmob_grob (smob);
824 extract_grob_set (me, "rests", rests);
825 if (rests.size ())
827 Grob *rest = rests.back ();
828 Real r = rest->extent (rest, X_AXIS).center ();
829 return scm_from_double (r);
833 if (Grob *f = first_head (me))
835 Interval head_wid = f->extent (f, X_AXIS);
836 Real attach = 0.0;
838 if (is_invisible (me))
839 attach = 0.0;
840 else
841 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
843 Direction d = get_grob_direction (me);
844 Real real_attach = head_wid.linear_combination (d * attach);
845 Real r = real_attach;
847 /* If not centered: correct for stem thickness. */
848 if (attach)
850 Real rule_thick = thickness (me);
851 r += -d * rule_thick * 0.5;
853 return scm_from_double (r);
856 programming_error ("Weird stem.");
857 return scm_from_double (0.0);
860 Spanner *
861 Stem::get_beam (Grob *me)
863 SCM b = me->get_object ("beam");
864 return dynamic_cast<Spanner *> (unsmob_grob (b));
867 Stem_info
868 Stem::get_stem_info (Grob *me)
870 Stem_info si;
871 si.dir_ = get_grob_direction (me);
873 SCM scm_info = me->get_property ("stem-info");
874 si.ideal_y_ = scm_to_double (scm_car (scm_info));
875 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
876 return si;
879 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
881 Stem::calc_stem_info (SCM smob)
883 Grob *me = unsmob_grob (smob);
884 Direction my_dir = get_grob_direction (me);
886 if (!my_dir)
888 programming_error ("no stem dir set");
889 my_dir = UP;
892 Real staff_space = Staff_symbol_referencer::staff_space (me);
893 Grob *beam = get_beam (me);
895 if (beam)
897 (void) beam->get_property ("beaming");
900 Real beam_translation = Beam::get_beam_translation (beam);
901 Real beam_thickness = Beam::get_beam_thickness (beam);
902 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
903 Real length_fraction
904 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
906 /* Simple standard stem length */
907 SCM details = me->get_property ("details");
908 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
910 Real ideal_length
911 = (scm_is_pair (lengths)
912 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
913 * staff_space
914 * length_fraction
916 stem only extends to center of beam
918 - 0.5 * beam_thickness)
919 : 0.0);
921 /* Condition: sane minimum free stem length (chord to beams) */
922 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
923 details, SCM_EOL);
925 Real ideal_minimum_free
926 = (scm_is_pair (lengths)
927 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
928 * staff_space
929 * length_fraction)
930 : 0.0);
932 Real height_of_my_trem = 0.0;
933 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
934 if (trem)
936 height_of_my_trem
937 = Stem_tremolo::vertical_length (trem)
938 /* hack a bit of space around the trem. */
939 + beam_translation;
943 /* UGH
944 It seems that also for ideal minimum length, we must use
945 the maximum beam count (for this direction):
947 \score { \relative c'' { a8[ a32] } }
949 must be horizontal. */
950 Real height_of_my_beams = beam_thickness
951 + (beam_count - 1) * beam_translation;
953 Real ideal_minimum_length = ideal_minimum_free
954 + height_of_my_beams
955 + height_of_my_trem
956 /* stem only extends to center of beam */
957 - 0.5 * beam_thickness;
959 ideal_length = max (ideal_length, ideal_minimum_length);
961 /* Convert to Y position, calculate for dir == UP */
962 Real note_start
963 = /* staff positions */
964 head_positions (me)[my_dir] * 0.5
965 * my_dir * staff_space;
966 Real ideal_y = note_start + ideal_length;
968 /* Conditions for Y position */
970 /* Lowest beam of (UP) beam must never be lower than second staffline
972 Reference?
974 Although this (additional) rule is probably correct,
975 I expect that highest beam (UP) should also never be lower
976 than middle staffline, just as normal stems.
978 Reference?
980 Obviously not for grace beams.
982 Also, not for knees. Seems to be a good thing. */
983 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
984 bool is_knee = to_boolean (beam->get_property ("knee"));
985 if (!no_extend && !is_knee)
987 /* Highest beam of (UP) beam must never be lower than middle
988 staffline */
989 ideal_y = max (ideal_y, 0.0);
990 /* Lowest beam of (UP) beam must never be lower than second staffline */
991 ideal_y = max (ideal_y, (-staff_space
992 - beam_thickness + height_of_my_beams));
995 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
997 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
998 details, SCM_EOL);
1000 Real minimum_free
1001 = (scm_is_pair (bemfl)
1002 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1003 * staff_space
1004 * length_fraction)
1005 : 0.0);
1007 Real minimum_length = max (minimum_free, height_of_my_trem)
1008 + height_of_my_beams
1009 /* stem only extends to center of beam */
1010 - 0.5 * beam_thickness;
1012 ideal_y *= my_dir;
1013 Real minimum_y = note_start + minimum_length;
1014 Real shortest_y = minimum_y * my_dir;
1016 return scm_list_2 (scm_from_double (ideal_y),
1017 scm_from_double (shortest_y));
1020 Slice
1021 Stem::beam_multiplicity (Grob *stem)
1023 SCM beaming = stem->get_property ("beaming");
1024 Slice le = int_list_to_slice (scm_car (beaming));
1025 Slice ri = int_list_to_slice (scm_cdr (beaming));
1026 le.unite (ri);
1027 return le;
1030 bool
1031 Stem::is_cross_staff (Grob *stem)
1033 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1034 return beam && Beam::is_cross_staff (beam);
1037 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1039 Stem::calc_cross_staff (SCM smob)
1041 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1044 /* FIXME: Too many properties */
1045 ADD_INTERFACE (Stem,
1046 "The stem represents the graphical stem. In addition, it"
1047 " internally connects note heads, beams, and tremolos. Rests"
1048 " and whole notes have invisible stems.\n"
1049 "\n"
1050 "The following properties may be set in the @code{details}"
1051 " list.\n"
1052 "\n"
1053 "@table @code\n"
1054 "@item beamed-lengths\n"
1055 "List of stem lengths given beam multiplicity.\n"
1056 "@item beamed-minimum-free-lengths\n"
1057 "List of normal minimum free stem lengths (chord to beams)"
1058 " given beam multiplicity.\n"
1059 "@item beamed-extreme-minimum-free-lengths\n"
1060 "List of extreme minimum free stem lengths (chord to beams)"
1061 " given beam multiplicity.\n"
1062 "@item lengths\n"
1063 "Default stem lengths. The list gives a length for each"
1064 " flag count.\n"
1065 "@item stem-shorten\n"
1066 "How much a stem in a forced direction should be shortened."
1067 " The list gives an amount depending on the number of flags"
1068 " and beams.\n"
1069 "@end table\n",
1071 /* properties */
1072 "avoid-note-head "
1073 "beam "
1074 "beaming "
1075 "beamlet-default-length "
1076 "beamlet-max-length-proportion "
1077 "default-direction "
1078 "details "
1079 "direction "
1080 "duration-log "
1081 "flag "
1082 "flag-style "
1083 "french-beaming "
1084 "length "
1085 "length-fraction "
1086 "max-beam-connect "
1087 "neutral-direction "
1088 "no-stem-extend "
1089 "note-heads "
1090 "positioning-done "
1091 "rests "
1092 "stem-end-position "
1093 "stem-info "
1094 "stemlet-length "
1095 "stroke-style "
1096 "thickness "
1097 "tremolo-flag "
1100 /****************************************************************/
1102 Stem_info::Stem_info ()
1104 ideal_y_ = shortest_y_ = 0;
1105 dir_ = CENTER;
1108 void
1109 Stem_info::scale (Real x)
1111 ideal_y_ *= x;
1112 shortest_y_ *= x;