Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / lily / stem.cc
blob04bc4105922e0ecfbca2ea41da058b007ff25fe7
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
7 TODO: This is way too hairy
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 staff_rad = Staff_symbol_referencer::staff_radius (me);
335 Real length = 7;
336 SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
337 if (scm_is_pair (s))
338 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
340 Direction dir = get_grob_direction (me);
342 /* Stems in unnatural (forced) direction should be shortened,
343 according to [Roush & Gourlay] */
344 Interval hp = head_positions (me);
345 if (dir && dir * hp[dir] >= 0)
347 SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
348 SCM scm_shorten = scm_is_pair (sshorten)
349 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
350 Real shorten_property = 2 * robust_scm2double (scm_shorten, 0);
351 /* change in length between full-size and shortened stems is executed gradually.
352 "transition area" = stems between full-sized and fully-shortened.
354 Real quarter_stem_length = 2 * scm_to_double (robust_list_ref (0, s));
355 /* shortening_step = difference in length between consecutive stem lengths
356 in transition area. The bigger the difference between full-sized
357 and shortened stems, the bigger shortening_step is.
358 (but not greater than 1/2 and not smaller than 1/4).
359 value 6 is heuristic; it determines the suggested transition slope steepnesas.
361 Real shortening_step = min (max (0.25, (shorten_property / 6)), 0.5);
362 /* Shortening of unflagged stems should begin on the first stem that sticks
363 more than 1 staffspace (2 units) out of the staff.
364 Shortening of flagged stems begins in the same moment as unflagged ones,
365 but not earlier than on the middle line note.
367 Real which_step = (min (1.0, quarter_stem_length - (2 * staff_rad) - 2.0)) + abs(hp[dir]);
368 Real shorten = min (max (0.0, (shortening_step * which_step)), shorten_property);
370 length -= shorten;
373 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
375 /* Tremolo stuff. */
376 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
377 if (t_flag && !unsmob_grob (me->get_object ("beam")))
379 /* Crude hack: add extra space if tremolo flag is there.
381 We can't do this for the beam, since we get into a loop
382 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
384 Real minlen = 1.0
385 + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
387 /* We don't want to add the whole extent of the flag because the trem
388 and the flag can overlap partly. beam_translation gives a good
389 approximation */
390 if (durlog >= 3)
392 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
393 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
394 minlen += 2 * (durlog - 1.5) * beam_trans;
396 /* up-stems need even a little more space to avoid collisions. This
397 needs to be in sync with the tremolo positioning code in
398 Stem_tremolo::print */
399 if (dir == UP)
400 minlen += beam_trans;
402 length = max (length, minlen + 1.0);
405 return scm_from_double (length);
407 /* The log of the duration (Number of hooks on the flag minus two) */
409 Stem::duration_log (Grob *me)
411 SCM s = me->get_property ("duration-log");
412 return (scm_is_number (s)) ? scm_to_int (s) : 2;
415 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
417 Stem::calc_positioning_done (SCM smob)
419 Grob *me = unsmob_grob (smob);
420 if (!head_count (me))
421 return SCM_BOOL_T;
423 me->set_property ("positioning-done", SCM_BOOL_T);
425 extract_grob_set (me, "note-heads", ro_heads);
426 vector<Grob*> heads (ro_heads);
427 vector_sort (heads, position_less);
428 Direction dir = get_grob_direction (me);
430 if (dir < 0)
431 reverse (heads);
433 Real thick = thickness (me);
435 Grob *hed = support_head (me);
436 if (!dir)
438 programming_error ("Stem dir must be up or down.");
439 dir = UP;
440 set_grob_direction (me, dir);
443 bool is_harmonic_centered = false;
444 for (vsize i = 0; i < heads.size (); i++)
445 is_harmonic_centered = is_harmonic_centered
446 || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
447 is_harmonic_centered = is_harmonic_centered && is_invisible (me);
449 Real w = hed->extent (hed, X_AXIS)[dir];
450 for (vsize i = 0; i < heads.size (); i++)
452 Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
454 if (is_harmonic_centered)
455 amount =
456 hed->extent (hed, X_AXIS).linear_combination (CENTER)
457 - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
459 heads[i]->translate_axis (amount, X_AXIS);
461 bool parity = true;
462 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
463 for (vsize i = 1; i < heads.size (); i++)
465 Real p = Staff_symbol_referencer::get_position (heads[i]);
466 Real dy = fabs (lastpos- p);
469 dy should always be 0.5, 0.0, 1.0, but provide safety margin
470 for rounding errors.
472 if (dy < 1.1)
474 if (parity)
476 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
478 Direction d = get_grob_direction (me);
480 Reversed head should be shifted ell-thickness, but this
481 looks too crowded, so we only shift ell-0.5*thickness.
483 This leads to assymetry: Normal heads overlap the
484 stem 100% whereas reversed heads only overlaps the
485 stem 50%
488 Real reverse_overlap = 0.5;
489 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
490 X_AXIS);
492 if (is_invisible (me))
493 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
494 X_AXIS);
496 /* TODO:
498 For some cases we should kern some more: when the
499 distance between the next or prev note is too large, we'd
500 get large white gaps, eg.
504 |X <- kern this.
510 parity = !parity;
512 else
513 parity = true;
515 lastpos = int (p);
518 return SCM_BOOL_T;
521 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
523 Stem::calc_direction (SCM smob)
525 Grob *me = unsmob_grob (smob);
526 Direction dir = CENTER;
527 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
529 SCM ignore_me = beam->get_property ("direction");
530 (void) ignore_me;
531 dir = get_grob_direction (me);
533 else
535 SCM dd = me->get_property ("default-direction");
536 dir = to_dir (dd);
537 if (!dir)
538 return me->get_property ("neutral-direction");
541 return scm_from_int (dir);
544 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
546 Stem::calc_default_direction (SCM smob)
548 Grob *me = unsmob_grob (smob);
550 Direction dir = CENTER;
551 int staff_center = 0;
552 Interval hp = head_positions (me);
553 if (!hp.is_empty ())
555 int udistance = (int) (UP * hp[UP] - staff_center);
556 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
558 dir = Direction (sign (ddistance - udistance));
561 return scm_from_int (dir);
565 MAKE_SCHEME_CALLBACK (Stem, height, 1);
567 Stem::height (SCM smob)
569 Grob *me = unsmob_grob (smob);
570 if (!is_normal_stem (me))
571 return ly_interval2scm (Interval ());
573 Direction dir = get_grob_direction (me);
575 Grob *beam = get_beam (me);
576 if (beam)
578 /* trigger set-stem-lengths. */
579 beam->get_property ("quantized-positions");
583 Can't get_stencil (), since that would cache stencils too early.
584 This causes problems with beams.
586 Stencil *stencil = unsmob_stencil (print (smob));
587 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
588 if (beam)
590 if (dir == CENTER)
592 programming_error ("no stem direction");
593 dir = UP;
595 iv[dir] += dir * Beam::get_beam_thickness (beam) * 0.5;
598 return ly_interval2scm (iv);
601 Real
602 Stem::stem_end_position (Grob *me)
604 return robust_scm2double (me->get_property ("stem-end-position"), 0);
607 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
609 Stem::calc_flag (SCM smob)
611 Grob *me = unsmob_grob (smob);
613 int log = duration_log (me);
615 TODO: maybe property stroke-style should take different values,
616 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
617 '() or "grace"). */
618 string flag_style;
620 SCM flag_style_scm = me->get_property ("flag-style");
621 if (scm_is_symbol (flag_style_scm))
622 flag_style = ly_symbol2string (flag_style_scm);
624 if (flag_style == "no-flag")
625 return Stencil ().smobbed_copy ();
627 bool adjust = true;
629 string staffline_offs;
630 if (flag_style == "mensural")
631 /* Mensural notation: For notes on staff lines, use different
632 flags than for notes between staff lines. The idea is that
633 flags are always vertically aligned with the staff lines,
634 regardless if the note head is on a staff line or between two
635 staff lines. In other words, the inner end of a flag always
636 touches a staff line.
639 if (adjust)
641 int p = (int) (rint (stem_end_position (me)));
642 staffline_offs
643 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
645 else
646 staffline_offs = "2";
648 else
649 staffline_offs = "";
651 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
652 string font_char = flag_style
653 + to_string (dir) + staffline_offs + to_string (log);
654 Font_metric *fm = Font_interface::get_default_font (me);
655 Stencil flag = fm->find_by_name ("flags." + font_char);
656 if (flag.is_empty ())
657 me->warning (_f ("flag `%s' not found", font_char));
659 SCM stroke_style_scm = me->get_property ("stroke-style");
660 if (scm_is_string (stroke_style_scm))
662 string stroke_style = ly_scm2string (stroke_style_scm);
663 if (!stroke_style.empty ())
665 string font_char = flag_style + to_string (dir) + stroke_style;
666 Stencil stroke = fm->find_by_name ("flags." + font_char);
667 if (stroke.is_empty ())
669 font_char = to_string (dir) + stroke_style;
670 stroke = fm->find_by_name ("flags." + font_char);
672 if (stroke.is_empty ())
673 me->warning (_f ("flag stroke `%s' not found", font_char));
674 else
675 flag.add_stencil (stroke);
679 return flag.smobbed_copy ();
683 Stencil
684 Stem::flag (Grob *me)
686 int log = duration_log (me);
687 if (log < 3
688 || unsmob_grob (me->get_object ("beam")))
689 return Stencil ();
691 if (!is_normal_stem (me))
692 return Stencil ();
694 // This get_property call already evaluates the scheme function with
695 // the grob passed as argument! Thus, we only have to check if a valid
696 // stencil is returned.
697 SCM flag_style_scm = me->get_property ("flag");
698 if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
699 return *flag;
700 } else {
701 return Stencil ();
705 MAKE_SCHEME_CALLBACK (Stem, width, 1);
707 Stem::width (SCM e)
709 Grob *me = unsmob_grob (e);
711 Interval r;
713 if (is_invisible (me))
714 r.set_empty ();
715 else if (unsmob_grob (me->get_object ("beam"))
716 || abs (duration_log (me)) <= 2)
718 r = Interval (-1, 1);
719 r *= thickness (me) / 2;
721 else
723 r = Interval (-1, 1) * thickness (me) * 0.5;
724 r.unite (flag (me).extent (X_AXIS));
726 return ly_interval2scm (r);
729 Real
730 Stem::thickness (Grob *me)
732 return scm_to_double (me->get_property ("thickness"))
733 * Staff_symbol_referencer::line_thickness (me);
736 MAKE_SCHEME_CALLBACK (Stem, print, 1);
738 Stem::print (SCM smob)
740 Grob *me = unsmob_grob (smob);
741 Grob *beam = get_beam (me);
743 Stencil mol;
744 Direction d = get_grob_direction (me);
746 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
747 0.0);
748 bool stemlet = stemlet_length > 0.0;
750 /* TODO: make the stem start a direction ?
751 This is required to avoid stems passing in tablature chords. */
752 Grob *lh
753 = to_boolean (me->get_property ("avoid-note-head"))
754 ? last_head (me)
755 : first_head (me);
757 if (!lh && !stemlet)
758 return SCM_EOL;
760 if (!lh && stemlet && !beam)
761 return SCM_EOL;
763 if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1)
764 return SCM_EOL;
766 if (is_invisible (me))
767 return SCM_EOL;
769 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
770 Real y1 = y2;
771 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
773 if (lh)
774 y2 = Staff_symbol_referencer::get_position (lh);
775 else if (stemlet)
777 Real beam_translation = Beam::get_beam_translation (beam);
778 Real beam_thickness = Beam::get_beam_thickness (beam);
779 int beam_count = beam_multiplicity (me).length () + 1;
781 y2 -= d
782 * (0.5 * beam_thickness
783 + beam_translation * max (0, (beam_count - 1))
784 + stemlet_length) / half_space;
787 Interval stem_y (min (y1, y2), max (y2, y1));
789 if (Grob *head = support_head (me))
792 must not take ledgers into account.
794 Interval head_height = head->extent (head, Y_AXIS);
795 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
797 y_attach = head_height.linear_combination (y_attach);
798 stem_y[Direction (-d)] += d * y_attach / half_space;
801 // URG
802 Real stem_width = thickness (me);
803 Real blot
804 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
806 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
807 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
809 Stencil ss = Lookup::round_filled_box (b, blot);
810 mol.add_stencil (ss);
812 mol.add_stencil (get_translated_flag (me));
814 return mol.smobbed_copy ();
817 Stencil
818 Stem::get_translated_flag (Grob *me)
820 Stencil fl = flag (me);
821 if (!fl.is_empty ())
823 Direction d = get_grob_direction (me);
824 Real blot
825 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
826 Real stem_width = thickness (me);
827 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
828 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
829 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
830 fl.translate_axis (stem_width / 2, X_AXIS);
832 return fl;
837 move the stem to right of the notehead if it is up.
839 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
841 Stem::offset_callback (SCM smob)
843 Grob *me = unsmob_grob (smob);
845 extract_grob_set (me, "rests", rests);
846 if (rests.size ())
848 Grob *rest = rests.back ();
849 Real r = rest->extent (rest, X_AXIS).center ();
850 return scm_from_double (r);
854 if (Grob *f = first_head (me))
856 Interval head_wid = f->extent (f, X_AXIS);
857 Real attach = 0.0;
859 if (is_invisible (me))
860 attach = 0.0;
861 else
862 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
864 Direction d = get_grob_direction (me);
865 Real real_attach = head_wid.linear_combination (d * attach);
866 Real r = real_attach;
868 /* If not centered: correct for stem thickness. */
869 if (attach)
871 Real rule_thick = thickness (me);
872 r += -d * rule_thick * 0.5;
874 return scm_from_double (r);
877 programming_error ("Weird stem.");
878 return scm_from_double (0.0);
881 Spanner *
882 Stem::get_beam (Grob *me)
884 SCM b = me->get_object ("beam");
885 return dynamic_cast<Spanner *> (unsmob_grob (b));
888 Stem_info
889 Stem::get_stem_info (Grob *me)
891 Stem_info si;
892 si.dir_ = get_grob_direction (me);
894 SCM scm_info = me->get_property ("stem-info");
895 si.ideal_y_ = scm_to_double (scm_car (scm_info));
896 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
897 return si;
900 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
902 Stem::calc_stem_info (SCM smob)
904 Grob *me = unsmob_grob (smob);
905 Direction my_dir = get_grob_direction (me);
907 if (!my_dir)
909 programming_error ("no stem dir set");
910 my_dir = UP;
913 Real staff_space = Staff_symbol_referencer::staff_space (me);
914 Grob *beam = get_beam (me);
916 if (beam)
918 (void) beam->get_property ("beaming");
921 Real beam_translation = Beam::get_beam_translation (beam);
922 Real beam_thickness = Beam::get_beam_thickness (beam);
923 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
924 Real length_fraction
925 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
927 /* Simple standard stem length */
928 SCM details = me->get_property ("details");
929 SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
931 Real ideal_length
932 = (scm_is_pair (lengths)
933 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
934 * staff_space
935 * length_fraction
937 stem only extends to center of beam
939 - 0.5 * beam_thickness)
940 : 0.0);
942 /* Condition: sane minimum free stem length (chord to beams) */
943 lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
944 details, SCM_EOL);
946 Real ideal_minimum_free
947 = (scm_is_pair (lengths)
948 ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
949 * staff_space
950 * length_fraction)
951 : 0.0);
953 Real height_of_my_trem = 0.0;
954 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
955 if (trem)
957 height_of_my_trem
958 = Stem_tremolo::vertical_length (trem)
959 /* hack a bit of space around the trem. */
960 + beam_translation;
964 /* UGH
965 It seems that also for ideal minimum length, we must use
966 the maximum beam count (for this direction):
968 \score { \relative c'' { a8[ a32] } }
970 must be horizontal. */
971 Real height_of_my_beams = beam_thickness
972 + (beam_count - 1) * beam_translation;
974 Real ideal_minimum_length = ideal_minimum_free
975 + height_of_my_beams
976 + height_of_my_trem
977 /* stem only extends to center of beam */
978 - 0.5 * beam_thickness;
980 ideal_length = max (ideal_length, ideal_minimum_length);
982 /* Convert to Y position, calculate for dir == UP */
983 Real note_start
984 = /* staff positions */
985 head_positions (me)[my_dir] * 0.5
986 * my_dir * staff_space;
987 Real ideal_y = note_start + ideal_length;
989 /* Conditions for Y position */
991 /* Lowest beam of (UP) beam must never be lower than second staffline
993 Reference?
995 Although this (additional) rule is probably correct,
996 I expect that highest beam (UP) should also never be lower
997 than middle staffline, just as normal stems.
999 Reference?
1001 Obviously not for grace beams.
1003 Also, not for knees. Seems to be a good thing. */
1004 bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
1005 bool is_knee = to_boolean (beam->get_property ("knee"));
1006 if (!no_extend && !is_knee)
1008 /* Highest beam of (UP) beam must never be lower than middle
1009 staffline */
1010 ideal_y = max (ideal_y, 0.0);
1011 /* Lowest beam of (UP) beam must never be lower than second staffline */
1012 ideal_y = max (ideal_y, (-staff_space
1013 - beam_thickness + height_of_my_beams));
1016 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
1018 SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
1019 details, SCM_EOL);
1021 Real minimum_free
1022 = (scm_is_pair (bemfl)
1023 ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1024 * staff_space
1025 * length_fraction)
1026 : 0.0);
1028 Real minimum_length = max (minimum_free, height_of_my_trem)
1029 + height_of_my_beams
1030 /* stem only extends to center of beam */
1031 - 0.5 * beam_thickness;
1033 ideal_y *= my_dir;
1034 Real minimum_y = note_start + minimum_length;
1035 Real shortest_y = minimum_y * my_dir;
1037 return scm_list_2 (scm_from_double (ideal_y),
1038 scm_from_double (shortest_y));
1041 Slice
1042 Stem::beam_multiplicity (Grob *stem)
1044 SCM beaming = stem->get_property ("beaming");
1045 Slice le = int_list_to_slice (scm_car (beaming));
1046 Slice ri = int_list_to_slice (scm_cdr (beaming));
1047 le.unite (ri);
1048 return le;
1051 bool
1052 Stem::is_cross_staff (Grob *stem)
1054 Grob *beam = unsmob_grob (stem->get_object ("beam"));
1055 return beam && Beam::is_cross_staff (beam);
1058 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1060 Stem::calc_cross_staff (SCM smob)
1062 return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1065 /* FIXME: Too many properties */
1066 ADD_INTERFACE (Stem,
1067 "The stem represents the graphical stem. In addition, it"
1068 " internally connects note heads, beams, and tremolos. Rests"
1069 " and whole notes have invisible stems.\n"
1070 "\n"
1071 "The following properties may be set in the @code{details}"
1072 " list.\n"
1073 "\n"
1074 "@table @code\n"
1075 "@item beamed-lengths\n"
1076 "List of stem lengths given beam multiplicity.\n"
1077 "@item beamed-minimum-free-lengths\n"
1078 "List of normal minimum free stem lengths (chord to beams)"
1079 " given beam multiplicity.\n"
1080 "@item beamed-extreme-minimum-free-lengths\n"
1081 "List of extreme minimum free stem lengths (chord to beams)"
1082 " given beam multiplicity.\n"
1083 "@item lengths\n"
1084 "Default stem lengths. The list gives a length for each"
1085 " flag count.\n"
1086 "@item stem-shorten\n"
1087 "How much a stem in a forced direction should be shortened."
1088 " The list gives an amount depending on the number of flags"
1089 " and beams.\n"
1090 "@end table\n",
1092 /* properties */
1093 "avoid-note-head "
1094 "beam "
1095 "beaming "
1096 "beamlet-default-length "
1097 "beamlet-max-length-proportion "
1098 "default-direction "
1099 "details "
1100 "direction "
1101 "duration-log "
1102 "flag "
1103 "flag-style "
1104 "french-beaming "
1105 "length "
1106 "length-fraction "
1107 "max-beam-connect "
1108 "neutral-direction "
1109 "no-stem-extend "
1110 "note-heads "
1111 "positioning-done "
1112 "rests "
1113 "stem-end-position "
1114 "stem-info "
1115 "stemlet-length "
1116 "stroke-style "
1117 "thickness "
1118 "tremolo-flag "
1121 /****************************************************************/
1123 Stem_info::Stem_info ()
1125 ideal_y_ = shortest_y_ = 0;
1126 dir_ = CENTER;
1129 void
1130 Stem_info::scale (Real x)
1132 ideal_y_ *= x;
1133 shortest_y_ *= x;