Fix substitution error in shape noteheads
[lilypond/mpolesky.git] / lily / stem.cc
blob6bbeb162889776eebb2078056613432fe90f6eab
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_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);
262 else
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);
268 if (dir == UP)
269 iv = Interval (0, len);
270 else
271 iv = Interval (-len, 0);
273 if (!hp.is_empty ())
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 */
280 if (dir == UP)
281 iv[UP] = max (iv[UP], rad * ss);
282 else
283 iv[DOWN] = min (iv[DOWN], -rad * ss);
285 else
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");
306 vector<Real> a;
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)
318 stem_end = 0.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 length = 7;
335 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
336 if (scm_is_pair (s))
337 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
339 Direction dir = get_grob_direction (me);
341 /* Stems in unnatural (forced) direction should be shortened,
342 according to [Roush & Gourlay] */
343 Interval hp = head_positions (me);
344 if (dir && dir * hp[dir] >= 0)
346 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
347 SCM scm_shorten = scm_is_pair (sshorten)
348 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
349 Real shorten = 2* robust_scm2double (scm_shorten, 0);
351 /* On boundary: shorten only half */
352 if (abs (head_positions (me)[dir]) <= 1)
353 shorten *= 0.5;
355 length -= shorten;
358 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
360 /* Tremolo stuff. */
361 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
362 if (t_flag && !unsmob_grob (me->get_object ("beam")))
364 /* Crude hack: add extra space if tremolo flag is there.
366 We can't do this for the beam, since we get into a loop
367 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
369 Real minlen = 1.0
370 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
372 /* We don't want to add the whole extent of the flag because the trem
373 and the flag can overlap partly. beam_translation gives a good
374 approximation */
375 if (durlog >= 3)
377 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
378 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
379 minlen += 2 * (durlog - 1.5) * beam_trans;
381 /* up-stems need even a little more space to avoid collisions. This
382 needs to be in sync with the tremolo positioning code in
383 Stem_tremolo::print */
384 if (dir == UP)
385 minlen += beam_trans;
387 length = max (length, minlen + 1.0);
390 return scm_from_double (length);
392 /* The log of the duration (Number of hooks on the flag minus two) */
394 Stem::duration_log (Grob *me)
396 SCM s = me->get_property ("duration-log");
397 return (scm_is_number (s)) ? scm_to_int (s) : 2;
400 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
402 Stem::calc_positioning_done (SCM smob)
404 Grob *me = unsmob_grob (smob);
405 if (!head_count (me))
406 return SCM_BOOL_T;
408 me->set_property ("positioning-done", SCM_BOOL_T);
410 extract_grob_set (me, "note-heads", ro_heads);
411 vector<Grob*> heads (ro_heads);
412 vector_sort (heads, position_less);
413 Direction dir = get_grob_direction (me);
415 if (dir < 0)
416 reverse (heads);
418 Real thick = thickness (me);
420 Grob *hed = support_head (me);
421 if (!dir)
423 programming_error ("Stem dir must be up or down.");
424 dir = UP;
425 set_grob_direction (me, dir);
428 bool is_harmonic_centered = false;
429 for (vsize i = 0; i < heads.size (); i++)
430 is_harmonic_centered = is_harmonic_centered
431 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
432 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
434 Real w = hed->extent (hed, X_AXIS)[dir];
435 for (vsize i = 0; i < heads.size (); i++)
437 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
439 if (is_harmonic_centered)
440 amount =
441 hed->extent (hed, X_AXIS).linear_combination (CENTER)
442 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
444 heads[i]->translate_axis (amount, X_AXIS);
446 bool parity = true;
447 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
448 for (vsize i = 1; i < heads.size (); i++)
450 Real p = Staff_symbol_referencer::get_position (heads[i]);
451 Real dy = fabs (lastpos- p);
454 dy should always be 0.5, 0.0, 1.0, but provide safety margin
455 for rounding errors.
457 if (dy < 1.1)
459 if (parity)
461 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
463 Direction d = get_grob_direction (me);
465 Reversed head should be shifted ell-thickness, but this
466 looks too crowded, so we only shift ell-0.5*thickness.
468 This leads to assymetry: Normal heads overlap the
469 stem 100% whereas reversed heads only overlaps the
470 stem 50%
473 Real reverse_overlap = 0.5;
474 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
475 X_AXIS);
477 if (is_invisible (me))
478 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
479 X_AXIS);
481 /* TODO:
483 For some cases we should kern some more: when the
484 distance between the next or prev note is too large, we'd
485 get large white gaps, eg.
489 |X <- kern this.
495 parity = !parity;
497 else
498 parity = true;
500 lastpos = int (p);
503 return SCM_BOOL_T;
506 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
508 Stem::calc_direction (SCM smob)
510 Grob *me = unsmob_grob (smob);
511 Direction dir = CENTER;
512 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
514 SCM ignore_me = beam->get_property ("direction");
515 (void) ignore_me;
516 dir = get_grob_direction (me);
518 else
520 SCM dd = me->get_property ("default-direction");
521 dir = to_dir (dd);
522 if (!dir)
523 return me->get_property ("neutral-direction");
526 return scm_from_int (dir);
529 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
531 Stem::calc_default_direction (SCM smob)
533 Grob *me = unsmob_grob (smob);
535 Direction dir = CENTER;
536 int staff_center = 0;
537 Interval hp = head_positions (me);
538 if (!hp.is_empty ())
540 int udistance = (int) (UP * hp[UP] - staff_center);
541 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
543 dir = Direction (sign (ddistance - udistance));
546 return scm_from_int (dir);
550 MAKE_SCHEME_CALLBACK (Stem, height, 1);
552 Stem::height (SCM smob)
554 Grob *me = unsmob_grob (smob);
555 if (!is_normal_stem (me))
556 return ly_interval2scm (Interval ());
558 Direction dir = get_grob_direction (me);
560 Grob *beam = get_beam (me);
561 if (beam)
563 /* trigger set-stem-lengths. */
564 beam->get_property ("quantized-positions");
568 Can't get_stencil (), since that would cache stencils too early.
569 This causes problems with beams.
571 Stencil *stencil = unsmob_stencil (print (smob));
572 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
573 if (beam)
575 if (dir == CENTER)
577 programming_error ("no stem direction");
578 dir = UP;
580 iv[dir] += dir * Beam::get_beam_thickness (beam) * 0.5;
583 return ly_interval2scm (iv);
586 Real
587 Stem::stem_end_position (Grob *me)
589 return robust_scm2double (me->get_property ("stem-end-position"), 0);
592 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
594 Stem::calc_flag (SCM smob)
596 Grob *me = unsmob_grob (smob);
598 int log = duration_log (me);
600 TODO: maybe property stroke-style should take different values,
601 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
602 '() or "grace"). */
603 string flag_style;
605 SCM flag_style_scm = me->get_property ("flag-style");
606 if (scm_is_symbol (flag_style_scm))
607 flag_style = ly_symbol2string (flag_style_scm);
609 if (flag_style == "no-flag")
610 return Stencil ().smobbed_copy ();
612 bool adjust = true;
614 string staffline_offs;
615 if (flag_style == "mensural")
616 /* Mensural notation: For notes on staff lines, use different
617 flags than for notes between staff lines. The idea is that
618 flags are always vertically aligned with the staff lines,
619 regardless if the note head is on a staff line or between two
620 staff lines. In other words, the inner end of a flag always
621 touches a staff line.
624 if (adjust)
626 int p = (int) (rint (stem_end_position (me)));
627 staffline_offs
628 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
630 else
631 staffline_offs = "2";
633 else
634 staffline_offs = "";
636 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
637 string font_char = flag_style
638 + to_string (dir) + staffline_offs + to_string (log);
639 Font_metric *fm = Font_interface::get_default_font (me);
640 Stencil flag = fm->find_by_name ("flags." + font_char);
641 if (flag.is_empty ())
642 me->warning (_f ("flag `%s' not found", font_char));
644 SCM stroke_style_scm = me->get_property ("stroke-style");
645 if (scm_is_string (stroke_style_scm))
647 string stroke_style = ly_scm2string (stroke_style_scm);
648 if (!stroke_style.empty ())
650 string font_char = flag_style + to_string (dir) + stroke_style;
651 Stencil stroke = fm->find_by_name ("flags." + font_char);
652 if (stroke.is_empty ())
654 font_char = to_string (dir) + stroke_style;
655 stroke = fm->find_by_name ("flags." + font_char);
657 if (stroke.is_empty ())
658 me->warning (_f ("flag stroke `%s' not found", font_char));
659 else
660 flag.add_stencil (stroke);
664 return flag.smobbed_copy ();
668 Stencil
669 Stem::flag (Grob *me)
671 int log = duration_log (me);
672 if (log < 3
673 || unsmob_grob (me->get_object ("beam")))
674 return Stencil ();
676 if (!is_normal_stem (me))
677 return Stencil ();
679 // This get_property call already evaluates the scheme function with
680 // the grob passed as argument! Thus, we only have to check if a valid
681 // stencil is returned.
682 SCM flag_style_scm = me->get_property ("flag");
683 if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
684 return *flag;
685 } else {
686 return Stencil ();
690 MAKE_SCHEME_CALLBACK (Stem, width, 1);
692 Stem::width (SCM e)
694 Grob *me = unsmob_grob (e);
696 Interval r;
698 if (is_invisible (me))
699 r.set_empty ();
700 else if (unsmob_grob (me->get_object ("beam"))
701 || abs (duration_log (me)) <= 2)
703 r = Interval (-1, 1);
704 r *= thickness (me) / 2;
706 else
708 r = Interval (-1, 1) * thickness (me) * 0.5;
709 r.unite (flag (me).extent (X_AXIS));
711 return ly_interval2scm (r);
714 Real
715 Stem::thickness (Grob *me)
717 return scm_to_double (me->get_property ("thickness"))
718 * Staff_symbol_referencer::line_thickness (me);
721 MAKE_SCHEME_CALLBACK (Stem, print, 1);
723 Stem::print (SCM smob)
725 Grob *me = unsmob_grob (smob);
726 Grob *beam = get_beam (me);
728 Stencil mol;
729 Direction d = get_grob_direction (me);
731 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
732 0.0);
733 bool stemlet = stemlet_length > 0.0;
735 /* TODO: make the stem start a direction ?
736 This is required to avoid stems passing in tablature chords. */
737 Grob *lh
738 = to_boolean (me->get_property ("avoid-note-head"))
739 ? last_head (me)
740 : first_head (me);
742 if (!lh && !stemlet)
743 return SCM_EOL;
745 if (!lh && stemlet && !beam)
746 return SCM_EOL;
748 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
749 return SCM_EOL;
751 if (is_invisible (me))
752 return SCM_EOL;
754 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
755 Real y1 = y2;
756 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
758 if (lh)
759 y2 = Staff_symbol_referencer::get_position (lh);
760 else if (stemlet)
762 Real beam_translation = Beam::get_beam_translation (beam);
763 Real beam_thickness = Beam::get_beam_thickness (beam);
764 int beam_count = beam_multiplicity (me).length () + 1;
766 y2 -= d
767 * (0.5 * beam_thickness
768 + beam_translation * max (0, (beam_count - 1))
769 + stemlet_length) / half_space;
772 Interval stem_y (min (y1, y2), max (y2, y1));
774 if (Grob *head = support_head (me))
777 must not take ledgers into account.
779 Interval head_height = head->extent (head, Y_AXIS);
780 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
782 y_attach = head_height.linear_combination (y_attach);
783 stem_y[Direction (-d)] += d * y_attach / half_space;
786 // URG
787 Real stem_width = thickness (me);
788 Real blot
789 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
791 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
792 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
794 Stencil ss = Lookup::round_filled_box (b, blot);
795 mol.add_stencil (ss);
797 mol.add_stencil (get_translated_flag (me));
799 return mol.smobbed_copy ();
802 Stencil
803 Stem::get_translated_flag (Grob *me)
805 Stencil fl = flag (me);
806 if (!fl.is_empty ())
808 Direction d = get_grob_direction (me);
809 Real blot
810 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
811 Real stem_width = thickness (me);
812 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
813 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
814 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
815 fl.translate_axis (stem_width / 2, X_AXIS);
817 return fl;
822 move the stem to right of the notehead if it is up.
824 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
826 Stem::offset_callback (SCM smob)
828 Grob *me = unsmob_grob (smob);
830 extract_grob_set (me, "rests", rests);
831 if (rests.size ())
833 Grob *rest = rests.back ();
834 Real r = rest->extent (rest, X_AXIS).center ();
835 return scm_from_double (r);
839 if (Grob *f = first_head (me))
841 Interval head_wid = f->extent (f, X_AXIS);
842 Real attach = 0.0;
844 if (is_invisible (me))
845 attach = 0.0;
846 else
847 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
849 Direction d = get_grob_direction (me);
850 Real real_attach = head_wid.linear_combination (d * attach);
851 Real r = real_attach;
853 /* If not centered: correct for stem thickness. */
854 if (attach)
856 Real rule_thick = thickness (me);
857 r += -d * rule_thick * 0.5;
859 return scm_from_double (r);
862 programming_error ("Weird stem.");
863 return scm_from_double (0.0);
866 Spanner *
867 Stem::get_beam (Grob *me)
869 SCM b = me->get_object ("beam");
870 return dynamic_cast<Spanner *> (unsmob_grob (b));
873 Stem_info
874 Stem::get_stem_info (Grob *me)
876 Stem_info si;
877 si.dir_ = get_grob_direction (me);
879 SCM scm_info = me->get_property ("stem-info");
880 si.ideal_y_ = scm_to_double (scm_car (scm_info));
881 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
882 return si;
885 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
887 Stem::calc_stem_info (SCM smob)
889 Grob *me = unsmob_grob (smob);
890 Direction my_dir = get_grob_direction (me);
892 if (!my_dir)
894 programming_error ("no stem dir set");
895 my_dir = UP;
898 Real staff_space = Staff_symbol_referencer::staff_space (me);
899 Grob *beam = get_beam (me);
901 if (beam)
903 (void) beam->get_property ("beaming");
906 Real beam_translation = Beam::get_beam_translation (beam);
907 Real beam_thickness = Beam::get_beam_thickness (beam);
908 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
909 Real length_fraction
910 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
912 /* Simple standard stem length */
913 SCM details = me->get_property ("details");
914 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
916 Real ideal_length
917 = (scm_is_pair (lengths)
918 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
919 * staff_space
920 * length_fraction
922 stem only extends to center of beam
924 - 0.5 * beam_thickness)
925 : 0.0);
927 /* Condition: sane minimum free stem length (chord to beams) */
928 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
929 details, SCM_EOL);
931 Real ideal_minimum_free
932 = (scm_is_pair (lengths)
933 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
934 * staff_space
935 * length_fraction)
936 : 0.0);
938 Real height_of_my_trem = 0.0;
939 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
940 if (trem)
942 height_of_my_trem
943 = Stem_tremolo::vertical_length (trem)
944 /* hack a bit of space around the trem. */
945 + beam_translation;
949 /* UGH
950 It seems that also for ideal minimum length, we must use
951 the maximum beam count (for this direction):
953 \score { \relative c'' { a8[ a32] } }
955 must be horizontal. */
956 Real height_of_my_beams = beam_thickness
957 + (beam_count - 1) * beam_translation;
959 Real ideal_minimum_length = ideal_minimum_free
960 + height_of_my_beams
961 + height_of_my_trem
962 /* stem only extends to center of beam */
963 - 0.5 * beam_thickness;
965 ideal_length = max (ideal_length, ideal_minimum_length);
967 /* Convert to Y position, calculate for dir == UP */
968 Real note_start
969 = /* staff positions */
970 head_positions (me)[my_dir] * 0.5
971 * my_dir * staff_space;
972 Real ideal_y = note_start + ideal_length;
974 /* Conditions for Y position */
976 /* Lowest beam of (UP) beam must never be lower than second staffline
978 Reference?
980 Although this (additional) rule is probably correct,
981 I expect that highest beam (UP) should also never be lower
982 than middle staffline, just as normal stems.
984 Reference?
986 Obviously not for grace beams.
988 Also, not for knees. Seems to be a good thing. */
989 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
990 bool is_knee = to_boolean (beam->get_property ("knee"));
991 if (!no_extend && !is_knee)
993 /* Highest beam of (UP) beam must never be lower than middle
994 staffline */
995 ideal_y = max (ideal_y, 0.0);
996 /* Lowest beam of (UP) beam must never be lower than second staffline */
997 ideal_y = max (ideal_y, (-staff_space
998 - beam_thickness + height_of_my_beams));
1001 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
1003 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
1004 details, SCM_EOL);
1006 Real minimum_free
1007 = (scm_is_pair (bemfl)
1008 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1009 * staff_space
1010 * length_fraction)
1011 : 0.0);
1013 Real minimum_length = max (minimum_free, height_of_my_trem)
1014 + height_of_my_beams
1015 /* stem only extends to center of beam */
1016 - 0.5 * beam_thickness;
1018 ideal_y *= my_dir;
1019 Real minimum_y = note_start + minimum_length;
1020 Real shortest_y = minimum_y * my_dir;
1022 return scm_list_2 (scm_from_double (ideal_y),
1023 scm_from_double (shortest_y));
1026 Slice
1027 Stem::beam_multiplicity (Grob *stem)
1029 SCM beaming = stem->get_property ("beaming");
1030 Slice le = int_list_to_slice (scm_car (beaming));
1031 Slice ri = int_list_to_slice (scm_cdr (beaming));
1032 le.unite (ri);
1033 return le;
1036 bool
1037 Stem::is_cross_staff (Grob *stem)
1039 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1040 return beam && Beam::is_cross_staff (beam);
1043 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1045 Stem::calc_cross_staff (SCM smob)
1047 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1050 /* FIXME: Too many properties */
1051 ADD_INTERFACE (Stem,
1052 "The stem represents the graphical stem. In addition, it"
1053 " internally connects note heads, beams, and tremolos. Rests"
1054 " and whole notes have invisible stems.\n"
1055 "\n"
1056 "The following properties may be set in the @code{details}"
1057 " list.\n"
1058 "\n"
1059 "@table @code\n"
1060 "@item beamed-lengths\n"
1061 "List of stem lengths given beam multiplicity.\n"
1062 "@item beamed-minimum-free-lengths\n"
1063 "List of normal minimum free stem lengths (chord to beams)"
1064 " given beam multiplicity.\n"
1065 "@item beamed-extreme-minimum-free-lengths\n"
1066 "List of extreme minimum free stem lengths (chord to beams)"
1067 " given beam multiplicity.\n"
1068 "@item lengths\n"
1069 "Default stem lengths. The list gives a length for each"
1070 " flag count.\n"
1071 "@item stem-shorten\n"
1072 "How much a stem in a forced direction should be shortened."
1073 " The list gives an amount depending on the number of flags"
1074 " and beams.\n"
1075 "@end table\n",
1077 /* properties */
1078 "avoid-note-head "
1079 "beam "
1080 "beaming "
1081 "beamlet-default-length "
1082 "beamlet-max-length-proportion "
1083 "default-direction "
1084 "details "
1085 "direction "
1086 "duration-log "
1087 "flag "
1088 "flag-style "
1089 "french-beaming "
1090 "length "
1091 "length-fraction "
1092 "max-beam-connect "
1093 "neutral-direction "
1094 "no-stem-extend "
1095 "note-heads "
1096 "positioning-done "
1097 "rests "
1098 "stem-end-position "
1099 "stem-info "
1100 "stemlet-length "
1101 "stroke-style "
1102 "thickness "
1103 "tremolo-flag "
1106 /****************************************************************/
1108 Stem_info::Stem_info ()
1110 ideal_y_ = shortest_y_ = 0;
1111 dir_ = CENTER;
1114 void
1115 Stem_info::scale (Real x)
1117 ideal_y_ *= x;
1118 shortest_y_ *= x;